Commit Graph

630 Commits

Author SHA1 Message Date
Alex Waygood 3314cf90ed
[ty] Add more regression tests for `tuple` (#19974) 2025-08-18 18:30:05 +01:00
Aria Desires 0cb1abc1fc
[ty] Implement partial stubs (#19931)
Fixes https://github.com/astral-sh/ty/issues/184
2025-08-18 13:14:13 -04:00
Alex Waygood fbf24be8ae
[ty] Detect illegal multiple inheritance with `NamedTuple` (#19943) 2025-08-18 12:03:01 +00:00
Alex Waygood 4ac2b2c222
[ty] Have `SemanticIndex::place_table()` and `SemanticIndex::use_def_map` return references (#19944) 2025-08-18 11:30:52 +01:00
Alex Waygood ec3163781c
[ty] Remove unused code (#19949) 2025-08-17 18:54:24 +01:00
Douglas Creager b892e4548e
[ty] Track when type variables are inferable or not (#19786)
`Type::TypeVar` now distinguishes whether the typevar in question is
inferable or not.

A typevar is _not inferable_ inside the body of the generic class or
function that binds it:

```py
def f[T](t: T) -> T:
    return t
```

The infered type of `t` in the function body is `TypeVar(T,
NotInferable)`. This represents how e.g. assignability checks need to be
valid for all possible specializations of the typevar. Most of the
existing assignability/etc logic only applies to non-inferable typevars.

Outside of the function body, the typevar is _inferable_:

```py
f(4)
```

Here, the parameter type of `f` is `TypeVar(T, Inferable)`. This
represents how e.g. assignability doesn't need to hold for _all_
specializations; instead, we need to find the constraints under which
this specific assignability check holds.

This is in support of starting to perform specialization inference _as
part of_ performing the assignability check at the call site.

In the [[POPL2015][]] paper, this concept is called _monomorphic_ /
_polymorphic_, but I thought _non-inferable_ / _inferable_ would be
clearer for us.

Depends on #19784 

[POPL2015]: https://doi.org/10.1145/2676726.2676991

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2025-08-16 18:25:03 -04:00
Alex Waygood 9ac39cee98
[ty] Ban protocols from inheriting from non-protocol generic classes (#19941) 2025-08-16 19:38:43 +01:00
Alex Waygood f4d8826428
[ty] Fix error message for invalidly providing type arguments to `NamedTuple` when it occurs in a type expression (#19940) 2025-08-16 17:45:15 +00:00
Alex Waygood 26d6c3831f
[ty] Represent `NamedTuple` as an opaque special form, not a class (#19915) 2025-08-15 18:20:14 +01:00
Alex Waygood 9ced219ffc
[ty] Remove incorrect type narrowing for `if type(x) is C[int]` (#19926) 2025-08-15 17:52:14 +01:00
Alex Waygood 6de84ed56e
Add `else`-branch narrowing for `if type(a) is A` when `A` is `@final` (#19925) 2025-08-15 14:52:30 +01:00
github-actions[bot] bd4506aac5
[ty] Sync vendored typeshed stubs (#19923)
Close and reopen this PR to trigger CI

---------

Co-authored-by: typeshedbot <>
Co-authored-by: Carl Meyer <carl@astral.sh>
2025-08-14 18:09:35 -07:00
Shunsuke Shibayama 0e5577ab56
[ty] fix lazy snapshot sweeping in nested scopes (#19908)
## Summary

This PR closes astral-sh/ty#955.

## Test Plan

New test cases in `narrowing/conditionals/nested.md`.
2025-08-14 17:52:52 -07:00
Andrii Turov 957320c0f1
[ty] Add diagnostics for invalid `await` expressions (#19711)
## Summary

This PR adds a new lint, `invalid-await`, for all sorts of reasons why
an object may not be `await`able, as discussed in astral-sh/ty#919.
Precisely, `__await__` is guarded against being missing, possibly
unbound, or improperly defined (expects additional arguments or doesn't
return an iterator).

Of course, diagnostics need to be fine-tuned. If `__await__` cannot be
called with no extra arguments, it indicates an error (or a quirk?) in
the method signature, not at the call site. Without any doubt, such an
object is not `Awaitable`, but I feel like talking about arguments for
an *implicit* call is a bit leaky.
I didn't reference any actual diagnostic messages in the lint
definition, because I want to hear feedback first.

Also, there's no mention of the actual required method signature for
`__await__` anywhere in the docs. The only reference I had is the
`typing` stub. I basically ended up linking `[Awaitable]` to ["must
implement
`__await__`"](https://docs.python.org/3/library/collections.abc.html#collections.abc.Awaitable),
which is insufficient on its own.

## Test Plan

The following code was tested:
```python
import asyncio
import typing


class Awaitable:
    def __await__(self) -> typing.Generator[typing.Any, None, int]:
        yield None
        return 5


class NoDunderMethod:
    pass


class InvalidAwaitArgs:
    def __await__(self, value: int) -> int:
        return value


class InvalidAwaitReturn:
    def __await__(self) -> int:
        return 5


class InvalidAwaitReturnImplicit:
    def __await__(self):
        pass


async def main() -> None:
    result = await Awaitable()  # valid
    result = await NoDunderMethod()  # `__await__` is missing
    result = await InvalidAwaitReturn()  # `__await__` returns `int`, which is not a valid iterator 
    result = await InvalidAwaitArgs()  # `__await__` expects additional arguments and cannot be called implicitly
    result = await InvalidAwaitReturnImplicit()  # `__await__` returns `Unknown`, which is not a valid iterator


asyncio.run(main())
```

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2025-08-14 14:38:33 -07:00
Alex Waygood f6093452ed
[ty] Synthesize read-only properties for all declared members on `NamedTuple` classes (#19899) 2025-08-14 21:25:45 +00:00
Alex Waygood 82350a398e
[ty] Remove use of `ClassBase::try_from_type` from `super()` machinery (#19902) 2025-08-14 22:14:31 +01:00
justin dc2e8ab377
[ty] support `kw_only=True` for `dataclass()` and `field()` (#19677)
## Summary
https://github.com/astral-sh/ty/issues/111

adds support for `@dataclass(kw_only=True)`
(https://docs.python.org/3/library/dataclasses.html)

## Test Plan
- new mdtests
- triaged conformance diffs (notes here:
https://diffswarm.dev/d-01k2gknwyq82f6x17zqf3apjxc)
- `mypy_primer` no-op
2025-08-14 08:02:55 -07:00
Alex Waygood 3288ac2dfb
[ty] Add caching to `CodeGeneratorKind::matches()` (#19912) 2025-08-14 11:54:11 +01:00
Carl Meyer 5a570c8e6d
[ty] fix deferred name loading in PEP695 generic classes/functions (#19888)
## Summary

For PEP 695 generic functions and classes, there is an extra "type
params scope" (a child of the outer scope, and wrapping the body scope)
in which the type parameters are defined; class bases and function
parameter/return annotations are resolved in that type-params scope.

This PR fixes some longstanding bugs in how we resolve name loads from
inside these PEP 695 type parameter scopes, and also defers type
inference of PEP 695 typevar bounds/constraints/default, so we can
handle cycles without panicking.

We were previously treating these type-param scopes as lazy nested
scopes, which is wrong. In fact they are eager nested scopes; the class
`C` here inherits `int`, not `str`, and previously we got that wrong:

```py
Base = int

class C[T](Base): ...

Base = str
```

But certain syntactic positions within type param scopes (typevar
bounds/constraints/defaults) are lazy at runtime, and we should use
deferred name resolution for them. This also means they can have cycles;
in order to handle that without panicking in type inference, we need to
actually defer their type inference until after we have constructed the
`TypeVarInstance`.

PEP 695 does specify that typevar bounds and constraints cannot be
generic, and that typevar defaults can only reference prior typevars,
not later ones. This reduces the scope of (valid from the type-system
perspective) cycles somewhat, although cycles are still possible (e.g.
`class C[T: list[C]]`). And this is a type-system-only restriction; from
the runtime perspective an "invalid" case like `class C[T: T]` actually
works fine.

I debated whether to implement the PEP 695 restrictions as a way to
avoid some cycles up-front, but I ended up deciding against that; I'd
rather model the runtime name-resolution semantics accurately, and
implement the PEP 695 restrictions as a separate diagnostic on top.
(This PR doesn't yet implement those diagnostics, thus some `# TODO:
error` in the added tests.)

Introducing the possibility of cyclic typevars made typevar display
potentially stack overflow. For now I've handled this by simply removing
typevar details (bounds/constraints/default) from typevar display. This
impacts display of two kinds of types. If you `reveal_type(T)` on an
unbound `T` you now get just `typing.TypeVar` instead of
`typing.TypeVar("T", ...)` where `...` is the bound/constraints/default.
This matches pyright and mypy; pyrefly uses `type[TypeVar[T]]` which
seems a bit confusing, but does include the name. (We could easily
include the name without cycle issues, if there's a syntax we like for
that.)

It also means that displaying a generic function type like `def f[T:
int](x: T) -> T: ...` now displays as `f[T](x: T) -> T` instead of `f[T:
int](x: T) -> T`. This matches pyright and pyrefly; mypy does include
bound/constraints/defaults of typevars in function/callable type
display. If we wanted to add this, we would either need to thread a
visitor through all the type display code, or add a `decycle` type
transformation that replaced recursive reoccurrence of a type with a
marker.

## Test Plan

Added mdtests and modified existing tests to improve their correctness.

After this PR, there's only a single remaining py-fuzzer seed in the
0-500 range that panics! (Before this PR, there were 10; the fuzzer
likes to generate cyclic PEP 695 syntax.)

## Ecosystem report

It's all just the changes to `TypeVar` display.
2025-08-13 15:51:59 -07:00
Douglas Creager baadb5a78d
[ty] Add some additional type safety to `CycleDetector` (#19903)
This PR adds a type tag to the `CycleDetector` visitor (and its
aliases).

There are some places where we implement e.g. an equivalence check by
making a disjointness check. Both `is_equivalent_to` and
`is_disjoint_from` use a `PairVisitor` to handle cycles, but they should
not use the same visitor. I was finding it tedious to remember when it
was appropriate to pass on a visitor and when not to. This adds a
`PhantomData` type tag to ensure that we can't pass on one method's
visitor to a different method.

For `has_relation` and `apply_type_mapping`, we have an existing type
that we can use as the tag. For the other methods, I've added empty
structs (`Normalized`, `IsDisjointFrom`, `IsEquivalentTo`) to use as
tags.
2025-08-13 17:32:35 -04:00
Aria Desires f0b03c3e86
[ty] resolve docstrings for modules (#19898)
This also reintroduces the `ResolvedDefinition::Module` variant because
reverse-engineering it in several places is a bit confusing. In an ideal
world we wouldn't have `ResolvedDefinition::FileWithRange` as it kinda
kills the ability to do richer analysis, so I want to chip away at its
scope wherever I can (currently it's used to point at asname parts of
import statements when doing `ImportAliasResolution::PreserveAliases`,
and also keyword arguments).

This also makes a kind of odd change to allow a hover to *only* produce
a docstring. This works around an oddity where hovering over a module
name in an import fails to resolve to a `ty` even though hovering over
uses of that imported name *does*.

The two fixed tests reflect the two interesting cases here.
2025-08-13 12:24:01 -04:00
Alex Waygood 9f6146a13d
[ty] Add precise inference for indexing, slicing and unpacking `NamedTuple` instances (#19560)
Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2025-08-13 15:19:44 +00:00
Carl Meyer e12747a903
[ty] simplify return type of place_from_declarations (#19884)
## Summary

A [passing
comment](https://github.com/astral-sh/ruff/pull/19711#issuecomment-3169312014)
led me to explore why we didn't report a class attribute as possibly
unbound if it was a method and defined in two different conditional
branches.

I found that the reason was because of our handling of "conflicting
declarations" in `place_from_declarations`. It returned a `Result` which
would be `Err` in case of conflicting declarations.

But we only actually care about conflicting declarations when we are
actually doing type inference on that scope and might emit a diagnostic
about it. And in all cases (including that one), we want to otherwise
proceed with the union of the declared types, as if there was no
conflict.

In several cases we were failing to handle the union of declared types
in the same way as a normal declared type if there was a declared-types
conflict. The `Result` return type made this mistake really easy to
make, as we'd match on e.g. `Ok(Place::Type(...))` and do one thing,
then match on `Err(...)` and do another, even though really both of
those cases should be handled the same.

This PR refactors `place_from_declarations` to instead return a struct
which always represents the declared type we should use in the same way,
as well as carrying the conflicting declared types, if any. This struct
has a method to allow us to explicitly ignore the declared-types
conflict (which is what we want in most cases), as well as a method to
get the declared type and the conflict information, in the case where we
want to emit a diagnostic on the conflict.

## Test Plan

Existing CI; added a test showing that we now understand a
multiply-conditionally-defined method as possibly-unbound.

This does trigger issues on a couple new fuzzer seeds, but the issues
are just new instances of an already-known (and rarely occurring)
problem which I already plan to address in a future PR, so I think it's
OK to land as-is.

I happened to build this initially on top of
https://github.com/astral-sh/ruff/pull/19711, which adds invalid-await
diagnostics, so I also updated some invalid-syntax tests to not await on
an invalid type, since the purpose of those tests is to check the
syntactic location of the `await`, not the validity of the awaited type.
2025-08-13 14:17:08 +00:00
Alex Waygood 5725c4b17f
[ty] Various minor cleanups to tuple internals (#19891) 2025-08-13 13:46:22 +00:00
Alex Waygood 2f3c7ad1fc
[ty] Improve `sys.version_info` special casing (#19894) 2025-08-13 14:39:13 +01:00
Carl Meyer 13bdba5d28
[ty] support recursive type aliases (#19805)
## Summary

Support recursive type aliases by adding a `Type::TypeAlias` type
variant, which allows referring to a type alias directly as a type
without eagerly unpacking it to its value.

We still unpack type aliases when they are added to intersections and
unions, so that we can simplify the intersection/union appropriately
based on the unpacked value of the type alias.

This introduces new possible recursive types, and so also requires
expanding our usage of recursion-detecting visitors in Type methods. The
use of these visitors is still not fully comprehensive in this PR, and
will require further expansion to support recursion in more kinds of
types (I already have further work on this locally), but I think it may
be better to do this incrementally in multiple PRs.

## Test Plan

Added some recursive type-alias tests and made them pass.
2025-08-12 09:03:10 -07:00
Alex Waygood d76fd103ae
[ty] Remove unsafe `salsa::Update` implementations in `tuple.rs` (#19880) 2025-08-12 15:53:34 +01:00
Matthew Mckee ad28b80f96
[ty] Function argument inlay hints (#19269) 2025-08-12 13:56:54 +00:00
Alex Waygood 3458f365da
[ty] Remove Salsa interning for `TypedDictType` (#19879) 2025-08-12 14:35:26 +01:00
Alex Waygood 498a04804d
[ty] Reduce memory usage of `TupleSpec` and `TupleType` (#19872) 2025-08-12 12:51:16 +01:00
Ibraheem Ahmed f34b65b7a0
[ty] Track heap usage of salsa structs (#19790)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-08-12 13:28:44 +02:00
Carl Meyer 28820db1cd
[ty] simplify CycleDetector::visit signature (#19873)
## Summary

After https://github.com/astral-sh/ruff/pull/19871, I realized that now
that we are passing around shared references to `CycleDetector`
visitors, we can now also simplify the `visit` callback signature; we
don't need to smuggle a single visitor reference through it anymore.
This is a pretty minor simplification, and it doesn't really make
anything shorter since I typically used a very short name (`v`) for the
smuggled reference, but I think it reduces cognitive overhead in reading
these `visit` usages; the extra variable would likely be confusing
otherwise for a reader.

## Test Plan

Existing CI.
2025-08-11 17:12:26 -07:00
Carl Meyer ea1aa9ebfe
[ty] use interior mutability in type visitors (#19871)
## Summary

Type visitors are conceptually immutable, they just internally track the
types they've seen (and some maintain a cache of results.) Passing
around mutable visitors everywhere can get us into borrow-checker
trouble in some cases, where we need to recursively pass along the
visitor inside more than one closure with non-disjoint lifetime.

Use interior mutability (via `RefCell` and `Cell`) inside the visitors
instead, to allow us to pass around shared references.

## Test Plan

Existing tests.
2025-08-11 15:42:53 -07:00
Alex Waygood d2fbf2af8f
[ty] Remove `Type::Tuple` (#19669) 2025-08-11 22:03:32 +01:00
Micha Reiser 2abd683376
[ty] Short circuit `ReachabilityConstraints::analyze_single` for dynamic types (#19867) 2025-08-11 21:58:34 +02:00
Douglas Creager dc84645c36
[ty] Use separate Rust types for bound and unbound type variables (#19796)
This PR creates separate Rust types for bound and unbound type
variables, as proposed in https://github.com/astral-sh/ty/issues/926.

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

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2025-08-11 15:29:58 -04:00
Alex Waygood 5a116e48c3
[ty] Add Salsa caching to `TupleType::to_class_type` (#19840) 2025-08-09 09:29:26 +01:00
Douglas Creager 3a542a80f6
[ty] Handle cycles when finding implicit attributes (#19833)
The [minimal
reproduction](https://gist.github.com/dcreager/fc53c59b30d7ce71d478dcb2c1c56444)
of https://github.com/astral-sh/ty/issues/948 is an example of a class
with implicit attributes whose types end up depending on themselves. Our
existing cycle detection for `infer_expression_types` is usually enough
to handle this situation correctly, but when there are very many of
these implicit attributes, we get a combinatorial explosion of running
time and memory usage.

Adding a separate cycle handler for `ClassLiteral::implicit_attribute`
lets us catch and recover from this situation earlier.

Closes https://github.com/astral-sh/ty/issues/948
2025-08-08 17:01:17 -04:00
Aria Desires 7cc3f1ebe9
[ty] Implement stdlib stub mapping (#19529)
by using essentially the same logic for system site-packages, on the
assumption that system site-packages are always a subdir of the stdlib
we were looking for.
2025-08-08 15:52:15 -04:00
Eric Jolibois 0095ff4c1a
[ty] Implement module-level `__getattr__` support (#19791)
fix https://github.com/astral-sh/ty/issues/943

## Summary

Add module-level `__getattr__` support for ty's type checker, fixing
issue https://github.com/astral-sh/ty/issues/943.
Module-level `__getattr__` functions ([PEP
562](https://peps.python.org/pep-0562/)) are now respected when
resolving dynamic attributes, matching the behavior of mypy and pyright.

## Implementation

Thanks @sharkdp for the guidance in
https://github.com/astral-sh/ty/issues/943#issuecomment-3157566579
- Adds module-specific `__getattr__` resolution in
`ModuleLiteral.static_member()`
- Maintains proper attribute precedence: explicit attributes >
submodules > `__getattr__`

## Test Plan
- New mdtest covering basic functionality, type annotations, attribute
precedence, and edge cases
(run ```cargo nextest run -p ty_python_semantic
mdtest__import_module_getattr```)
- All new tests pass, verifying `__getattr__` is called correctly and
returns proper types
  - Existing test suite passes, ensuring no regressions introduced
2025-08-08 10:39:37 -07:00
Alex Waygood 8489816edc
[ty] Improve ability to solve TypeVars when they appear in unions (#19829) 2025-08-08 17:50:37 +01:00
Micha Reiser fd35435281
[ty] Improve performance of subtyping and assignability checks for protocols (#19824) 2025-08-08 13:05:12 +02:00
Jack O'Connor 827456f977 [ty] more cases for the class body global fallback 2025-08-07 17:30:27 -07:00
Shunsuke Shibayama 462adfd0e6
[ty] fix incorrect member narrowing (#19802)
## Summary

Reported in:
https://github.com/astral-sh/ruff/pull/19795#issuecomment-3161981945

If a root expression is reassigned, narrowing on the member should be
invalidated, but there was an oversight in the current implementation.

This PR fixes that, and also removes some unnecessary handling.

## Test Plan

New tests cases in `narrow/conditionals/nested.md`.
2025-08-07 16:04:07 -07:00
Andrew Gallant d5e1b7983e
[ty] Fix static assertion size check (#19814)
A `Segment` has a `Box` in it, which has a platform dependent size.
Restrict the check to only 64-bit targets.
2025-08-07 13:38:16 -05:00
Micha Reiser 7dfde3b929
Update Rust toolchain to 1.89 (#19807) 2025-08-07 18:21:50 +02:00
Alex Waygood c401a6d86e
[ty] Add failing tests for tuple subclasses (#19803) 2025-08-07 13:11:15 +00:00
UnboundVariable b005cdb7ff
[ty] Implemented support for "rename" language server feature (#19551)
This PR adds support for the "rename" language server feature. It builds
upon existing functionality used for "go to references".

The "rename" feature involves two language server requests. The first is
a "prepare rename" request that determines whether renaming should be
possible for the identifier at the current offset. The second is a
"rename" request that returns a list of file ranges where the rename
should be applied.

Care must be taken when attempting to rename symbols that span files,
especially if the symbols are defined in files that are not part of the
project. We don't want to modify code in the user's Python environment
or in the vendored stub files.

I found a few bugs in the "go to references" feature when implementing
"rename", and those bug fixes are included in this PR.

---------

Co-authored-by: UnboundVariable <unbound@gmail.com>
2025-08-07 15:58:18 +05:30
Micha Reiser b96aa4605b
[ty] Reduce size of member table (#19572) 2025-08-07 11:16:04 +02:00
Matthew Mckee ef1802b94f
[ty] Repurpose `FunctionType.into_bound_method_type` to return `BoundMethodType` (#19793)
## Summary

As per our naming scheme (at least for callable types) this should
return a `BoundMethodType`, or be renamed, but it makes more sense to
change the return type.

I also ensure `ClassType.into_callable` returns a `Type::Callable` in
the changed branch.

Ideally we could return a `CallableType` from these `into_callable`
functions (and rename to `into_callable_type` but because of unions we
cannot do this.
2025-08-06 15:24:59 -07:00
David Peter 98df62db79
[ty] Validate writes to `TypedDict` keys (#19782)
## Summary

Validates writes to `TypedDict` keys, for example:

```py
class Person(TypedDict):
    name: str
    age: int | None


def f(person: Person):
    person["naem"] = "Alice"  # error: [invalid-key]

    person["age"] = "42"  # error: [invalid-assignment]
```

The new specialized `invalid-assignment` diagnostic looks like this:

<img width="1160" height="279" alt="image"
src="https://github.com/user-attachments/assets/51259455-3501-4829-a84e-df26ff90bd89"
/>

## Ecosystem analysis

As far as I can tell, all true positives!

There are some extremely long diagnostic messages. We should truncate
our display of overload sets somehow.

## Test Plan

New Markdown tests
2025-08-06 15:19:13 -07:00
Douglas Creager 585ce12ace
[ty] `typing.Self` is bound by the method, not the class (#19784)
This fixes our logic for binding a legacy typevar with its binding
context. (To recap, a legacy typevar starts out "unbound" when it is
first created, and each time it's used in a generic class or function,
we "bind" it with the corresponding `Definition`.)

We treat `typing.Self` the same as a legacy typevar, and so we apply
this binding logic to it too. Before, we were using the enclosing class
as its binding context. But that's not correct — it's the method where
`typing.Self` is used that binds the typevar. (Each invocation of the
method will find a new specialization of `Self` based on the specific
instance type containing the invoked method.)

This required plumbing through some additional state to the
`in_type_expression` method.

This also revealed that we weren't handling `Self`-typed instance
attributes correctly (but were coincidentally not getting the expected
false positive diagnostics).
2025-08-06 17:26:17 -04:00
Ibraheem Ahmed 21ac16db85
[ty] Avoid overcounting shared memory usage (#19773)
## Summary

Use a global tracker to avoid double counting `Arc` instances.
2025-08-06 15:32:02 -04:00
David Peter b96929ee19
[ty] Disallow `typing.TypedDict` in type expressions (#19777)
## Summary

Disallow `typing.TypedDict` in type expressions.

Related reference: https://github.com/python/mypy/issues/11030

## Test Plan

New Markdown tests, checked ecosystem and conformance test impact.
2025-08-06 15:58:35 +02:00
Alex Waygood 529d81daca
[ty] Improve subscript narrowing for "safe mutable classes" (#19781)
## Summary

This PR improves the `is_safe_mutable_class` function in `infer.rs` in
several ways:
- It uses `KnownClass::to_instance()` for all "safe mutable classes".
Previously, we were using `SpecialFormType::instance_fallback()` for
some variants -- I'm not totally sure why. Switching to
`KnownClass::to_instance()` for all "safe mutable classes" fixes a
number of TODOs in the `assignment.md` mdtest suite
- Rather than eagerly calling `.to_instance(db)` on all "safe mutable
classes" every time `is_safe_mutable_class` is called, we now only call
it lazily on each element, allowing us to short-circuit more
effectively.
- I removed the entry entirely for `TypedDict` from the list of "safe
mutable classes", as it's not correct.
`SpecialFormType::TypedDict.instance_fallback(db)` just returns an
instance type representing "any instance of `typing._SpecialForm`",
which I don't think was the intent of this code. No tests fail as a
result of removing this entry, as we already check separately whether an
object is an inhabitant of a `TypedDict` type (and consider that object
safe-mutable if so!).

## Test Plan

mdtests updated
2025-08-06 12:26:25 +01:00
David Peter 4887bdf205
[ty] Infer types for key-based access on TypedDicts (#19763)
## Summary

This PR adds type inference for key-based access on `TypedDict`s and a
new diagnostic for invalid subscript accesses:

```py
class Person(TypedDict):
    name: str
    age: int | None

alice = Person(name="Alice", age=25)

reveal_type(alice["name"])  # revealed: str
reveal_type(alice["age"])  # revealed: int | None

alice["naem"]  # Unknown key "naem" - did you mean "name"?
```

## Test Plan

Updated Markdown tests
2025-08-06 09:36:33 +02:00
Matthew Mckee 18ad2848e3
Display generic function signature properly (#19544)
## Summary

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

## Test Plan

Update mdtest

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2025-08-05 16:35:08 -07:00
Alex Waygood 4090297a11
[ty] Fix more false positives related to `Generic` or `Protocol` being subscripted with a `ParamSpec` or `TypeVarTuple` (#19764) 2025-08-05 15:45:56 +01:00
Simon Lamon 934fd37d2b
[ty] Diagnostics for async context managers (#19704)
## Summary

Implements diagnostics for async context managers. Fixes
https://github.com/astral-sh/ty/issues/918.

## Test Plan

Mdtests have been added.
2025-08-05 07:41:37 -07:00
Alex Waygood 7dccb6a98c
[ty] Improve effectiveness of `KnownClass` fast paths in `instance.rs` (#19762) 2025-08-05 13:26:14 +01:00
David Peter 948f3f856c
[ty] Fix attribute access on `TypedDict`s (#19758)
## Summary

This PR fixes a few inaccuracies in attribute access on `TypedDict`s. It
also changes the return type of `type(person)` to `type[dict[str,
object]]` if `person: Person` is an inhabitant of a `TypedDict`
`Person`. We still use `type[Person]` as the *meta type* of Person,
however (see reasoning
[here](https://github.com/astral-sh/ruff/pull/19733#discussion_r2253297926)).

## Test Plan

Updated Markdown tests.
2025-08-05 13:59:10 +02:00
Alex Waygood 3af0b31de3
[ty] Speedup the `known_class_doesnt_fallback_to_unknown_unexpectedly_on_low_python_version` test (#19760) 2025-08-05 11:55:11 +00:00
David Peter 7df7be5c7d
[ty] Keep track of type qualifiers in stub declarations without right-hand side (#19756)
## Summary

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

## Test Plan

Regression test
2025-08-05 12:07:05 +02:00
David Peter 14fbc2b167
[ty] New `Type` variant for `TypedDict` (#19733)
## Summary

This PR adds a new `Type::TypedDict` variant. Before this PR, we treated
`TypedDict`-based types as dynamic Todo-types, and I originally planned
to make this change a no-op. And we do in fact still treat that new
variant similar to a dynamic type when it comes to type properties such
as assignability and subtyping. But then I somehow tricked myself into
implementing some of the things correctly, so here we are. The two main
behavioral changes are: (1) we now also detect generic `TypedDict`s,
which removes a few false positives in the ecosystem, and (2) we now
support *attribute* access (not key-based indexing!) on these types,
i.e. we infer proper types for something like
`MyTypedDict.__required_keys__`. Nothing exciting yet, but gets the
infrastructure into place.

Note that with this PR, the type of (the type) `MyTypedDict` itself is
still represented as a `Type::ClassLiteral` or `Type::GenericAlias` (in
case `MyTypedDict` is generic). Only inhabitants of `MyTypedDict`
(instances of `dict` at runtime) are represented by `Type::TypedDict`.
We may want to revisit this decision in the future, if this turns out to
be too error-prone. Right now, we need to use `.is_typed_dict(db)` in
all the right places to distinguish between actual (generic) classes and
`TypedDict`s. But so far, it seemed unnecessary to add additional `Type`
variants for these as well.

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

## Ecosystem impact

The new diagnostics on `cloud-init` look like true positives to me.

## Test Plan

Updated and new Markdown tests
2025-08-05 11:19:49 +02:00
Shunsuke Shibayama 351121c5c5
[ty] fix incorrect lazy scope narrowing (#19744)
## Summary

This is a follow-up to #19321.

Narrowing constraints introduced in a class scope were not applied even
when they can be applied in lazy nested scopes. This PR fixes so that
they are now applied.
Conversely, there were cases where narrowing constraints were being
applied in places where they should not, so it is also fixed.

## Test Plan

Some TODOs in `narrow/conditionals/nested.md` are now work correctly.
2025-08-04 20:32:08 -07:00
Shunsuke Shibayama 64bcc8db2f
[ty] fix lookup order of class variables before they are defined (#19743)
## Summary

This is a follow-up to #19321.

If we try to access a class variable before it is defined, the variable
is looked up in the global scope, rather than in any enclosing scopes.

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

## Test Plan

New tests in `narrow/conditionals/nested.md`.
2025-08-04 20:21:28 -07:00
Alex Waygood 3a9341f7be
[ty] Remove false positives when subscripting `Generic` or `Protocol` with a `ParamSpec` or `TypeVarTuple` (#19749) 2025-08-04 21:42:46 +01:00
David Peter 739c94f95a
[ty] Support as-patterns in reachability analysis (#19728)
## Summary

Support `as` patterns in reachability analysis:

```py
from typing import assert_never


def f(subject: str | int):
    match subject:
        case int() as x:
            pass
        case str():
            pass
        case _:
            assert_never(subject)  # would previously emit an error
```

Note that we still don't support inferring correct types for the bound
name (`x`).

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

## Test Plan

New Markdown tests
2025-08-04 20:13:50 +02:00
Alex Waygood 41207ec901
[ty] Infer `type[tuple[int, str]]` as the meta-type of `tuple[int, str]` (#19741) 2025-08-04 13:10:47 +00:00
Alex Waygood bc6e8b58ce
[ty] Return `Option<TupleType>` from `infer_tuple_type_expression` (#19735)
## Summary

This PR reduces the virality of some of the `Todo` types in
`infer_tuple_type_expression`. Rather than inferring `Todo`, we instead
infer `tuple[Todo, ...]`. This reflects the fact that whatever the
contents of the slice in a `tuple[]` type expression, we would always
infer some kind of tuple type as the result of the type expression. Any
tuple type should be assignable to `tuple[Todo, ...]`, so this shouldn't
introduce any new false positives; this can be seen in the ecosystem
report.

As a result of the change, we are now able to enforce in the signature
of `Type::infer_tuple_type_expression` that it returns an
`Option<TupleType<'db>>`, which is more strongly typed and expresses
clearly the invariant that a tuple type expression should always be
inferred as a `tuple` type. To enable this, it was necessary to refactor
several `TupleType` constructors in `tuple.rs` so that they return
`Option<TupleType>` rather than `Type`; this means that callers of these
constructor functions are now free to either propagate the
`Option<TupleType<'db>>` or convert it to a `Type<'db>`.

## Test Plan

Mdtests updated.
2025-08-04 13:48:19 +01:00
Douglas Creager d37911685f
[ty] Correctly instantiate generic class that inherits `__init__` from generic base class (#19693)
This is subtle, and the root cause became more apparent with #19604,
since we now have many more cases of superclasses and subclasses using
different typevars. The issue is easiest to see in the following:

```py
class C[T]:
    def __init__(self, t: T) -> None: ...

class D[U](C[T]):
    pass

reveal_type(C(1))  # revealed: C[int]
reveal_type(D(1))  # should be: D[int]
```

When instantiating a generic class, the `__init__` method inherits the
generic context of that class. This lets our call binding machinery
infer a specialization for that context.

Prior to this PR, the instantiation of `C` worked just fine. Its
`__init__` method would inherit the `[T]` generic context, and we would
infer `{T = int}` as the specialization based on the argument
parameters.

It didn't work for `D`. The issue is that the `__init__` method was
inheriting the generic context of the class where `__init__` was defined
(here, `C` and `[T]`). At the call site, we would then infer `{T = int}`
as the specialization — but that wouldn't help us specialize `D[U]`,
since `D` does not have `T` in its generic context!

Instead, the `__init__` method should inherit the generic context of the
class that we are performing the lookup on (here, `D` and `[U]`). That
lets us correctly infer `{U = int}` as the specialization, which we can
successfully apply to `D[U]`.

(Note that `__init__` refers to `C`'s typevars in its signature, but
that's okay; our member lookup logic already applies the `T = U`
specialization when returning a member of `C` while performing a lookup
on `D`, transforming its signature from `(Self, T) -> None` to `(Self,
U) -> None`.)

Closes https://github.com/astral-sh/ty/issues/588
2025-08-01 15:29:18 -04:00
Douglas Creager 06cd249a9b
[ty] Track different uses of legacy typevars, including context when rendering typevars (#19604)
This PR introduces a few related changes:

- We now keep track of each time a legacy typevar is bound in a
different generic context (e.g. class, function), and internally create
a new `TypeVarInstance` for each usage. This means the rest of the code
can now assume that salsa-equivalent `TypeVarInstance`s refer to the
same typevar, even taking into account that legacy typevars can be used
more than once.

- We also go ahead and track the binding context of PEP 695 typevars.
That's _much_ easier to track since we have the binding context right
there during type inference.

- With that in place, we can now include the name of the binding context
when rendering typevars (e.g. `T@f` instead of `T`)
2025-08-01 12:20:32 -04:00
David Peter 48d5bd13fa
[ty] Initial test suite for `TypedDict` (#19686)
## Summary

Adds an initial set of tests based on the highest-priority items in
https://github.com/astral-sh/ty/issues/154. This is certainly not yet
exhaustive (required/non-required, `total`, and other things are
missing), but will be useful to measure progress on this feature.

## Test Plan

Checked intended behavior against runtime and other type checkers.
2025-08-01 16:56:02 +02:00
Alex Waygood e7e7b7bf21
[ty] Improve debuggability of protocol types (#19662) 2025-08-01 15:16:13 +01:00
Alex Waygood 57e2e8664f
[ty] Simplify lifetime requirements for `PySlice` trait (#19687) 2025-08-01 15:13:47 +01:00
Alex Waygood 18aae21b9a
[ty] Improve `isinstance()` truthiness analysis for generic types (#19668) 2025-08-01 14:44:22 +01:00
David Peter ade6a4262a
[ty] Remove `Specialization::display` (full) (#19682)
## Summary

This seems to be unused
2025-08-01 10:44:11 +02:00
David Peter d43e6fb9c6
[ty] Remove `KnownModule::is_enum` (#19681)
## Summary

Changes the visibility of `KnownModule` and removes an unneeded
function.
2025-08-01 10:31:12 +02:00
Matthew Mckee b30d97e5e0
[ty] Support `__setitem__` and improve `__getitem__` related diagnostics (#19578)
## Summary

Adds validation to subscript assignment expressions.

```py
class Foo: ...

class Bar:
    __setattr__ = None

class Baz:
    def __setitem__(self, index: str, value: int) -> None:
        pass

# We now emit a diagnostic on these statements
Foo()[1] = 2
Bar()[1] = 2
Baz()[1] = 2

```

Also improves error messages on invalid `__getitem__` expressions

## Test Plan

Update mdtests and add more to `subscript/instance.md`

---------

Co-authored-by: David Peter <sharkdp@users.noreply.github.com>
Co-authored-by: David Peter <mail@david-peter.de>
2025-08-01 09:23:27 +02:00
Alex Waygood 2ab1502e51
[ty] Improve the `Display` for generic `type[]` types (#19667) 2025-07-31 19:45:01 +01:00
Alex Waygood a3f28baab4
[ty] Refactor `TypeInferenceBuilder::infer_subscript_expression_types` (#19658) 2025-07-31 13:38:43 +00:00
Brent Westbrook a71513bae1
Fix tests on 32-bit architectures (#19652)
Summary
--

Fixes #19640. I'm not sure these are the exact fixes we really want, but
I
reproduced the issue in a 32-bit Docker container and tracked down the
causes,
so I figured I'd open a PR.

As I commented on the issue, the `goto_references` test depends on the
iteration
order of the files in an `FxHashSet` in `Indexed`. In this case, we can
just
sort the output in test code.

Similarly, the tuple case depended on the order of overloads inserted in
an
`FxHashMap`. `FxIndexMap` seemed like a convenient drop-in replacement,
but I
don't know if that will have other detrimental effects. I did have to
change the
assertion for the tuple test, but I think it should now be stable across
architectures.

Test Plan
--

Running the tests in the aforementioned Docker container
2025-07-31 08:52:19 -04:00
Alex Waygood d2d4b115e3
[ty] Move `pandas-stubs` to bad.txt (#19659) 2025-07-31 12:33:24 +01:00
Alex Waygood 27b03a9d7b
[ty] Remove special casing for string-literal-in-tuple `__contains__` (#19642) 2025-07-31 11:28:03 +01:00
Ibraheem Ahmed 8f8c39c435
Simplify `get_size2` usage (#19643)
## Summary

These were added in the 0.5.0 release.
2025-07-30 15:31:37 -04:00
Matthew Mckee 4739bc8d14
[ty] Fix incorrect diagnostic when calling `__setitem__` (#19645)
## Summary

Resolves https://github.com/astral-sh/ty/issues/862 by not emitting a
diagnostic.

## Test Plan

Add test to show we don't emit the diagnostic
2025-07-30 20:34:52 +02:00
Alex Waygood 7b4103bcb6
[ty] Remove special casing for tuple addition (#19636) 2025-07-30 16:25:42 +00:00
Alex Waygood ec3d5ebda2
[ty] Upcast heterogeneous and mixed tuples to homogeneous tuples where it's necessary to solve a `TypeVar` (#19635)
## Summary

This PR improves our generics solver such that we are able to solve the
`TypeVar` in this snippet to `int | str` (the union of the elements in
the heterogeneous tuple) by upcasting the heterogeneous tuple to its
pure-homogeneous-tuple supertype:

```py
def f[T](x: tuple[T, ...]) -> T:
    return x[0]

def g(x: tuple[int, str]):
    reveal_type(f(x))
```

## Test Plan

Mdtests. Some TODOs remain in the mdtest regarding solving `TypeVar`s
for mixed tuples, but I think this PR on its own is a significant step
forward for our generics solver when it comes to tuple types.

---------

Co-authored-by: Douglas Creager <dcreager@dcreager.net>
2025-07-30 17:12:21 +01:00
David Peter eb02aa5676
[ty] Async for loops and async iterables (#19634)
## Summary

Add support for `async for` loops and async iterables.

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

## Ecosystem impact

```diff
- boostedblob/listing.py:445:54: warning[unused-ignore-comment] Unused blanket `type: ignore` directive
```

This is correct. We now find a true positive in the `# type: ignore`'d
code.

All of the other ecosystem hits are of the type

```diff
trio (https://github.com/python-trio/trio)
+ src/trio/_core/_tests/test_guest_mode.py:532:24: error[not-iterable] Object of type `MemorySendChannel[int] | MemoryReceiveChannel[int]` may not be iterable
```

The message is correct, because only `MemoryReceiveChannel` has an
`__aiter__` method, but `MemorySendChannel` does not. What's not correct
is our inferred type here. It should be `MemoryReceiveChannel[int]`, not
the union of the two. This is due to missing unpacking support for tuple
subclasses, which @AlexWaygood is working on. I don't think this should
block merging this PR, because those wrong types are already there,
without this PR.

## Test Plan

New Markdown tests and snapshot tests for diagnostics.
2025-07-30 17:40:24 +02:00
Andrew Gallant d1a286226c [ty] Update module resolution diagram to account for typeshed `VERSIONS` file
This does unfortunately add a fair bit of complexity to the flow
diagram.

Ref https://github.com/astral-sh/ruff/pull/19620#issuecomment-3133684294
2025-07-30 10:34:58 -04:00
Alex Waygood feaedb1812
[ty] Synthesize precise `__getitem__` overloads for tuple subclasses (#19493) 2025-07-30 11:25:44 +00:00
David Peter 4ecf1d205a
[ty] Support `async`/`await`, `async with` and `yield from` (#19595)
## Summary

- Add support for the return types of `async` functions
- Add type inference for `await` expressions
- Add support for `async with` / async context managers
- Add support for `yield from` expressions

This PR is generally lacking proper error handling in some cases (e.g.
illegal `__await__` attributes). I'm planning to work on this in a
follow-up.

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

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

## Ecosystem

There are a lot of true positives on `prefect` which look similar to:
```diff
prefect (https://github.com/PrefectHQ/prefect)
+ src/integrations/prefect-aws/tests/workers/test_ecs_worker.py:406:12: error[unresolved-attribute] Type `str` has no attribute `status_code`
```

This is due to a wrong return type annotation
[here](e926b8c4c1/src/integrations/prefect-aws/tests/workers/test_ecs_worker.py (L355-L391)).

```diff
mitmproxy (https://github.com/mitmproxy/mitmproxy)
+ test/mitmproxy/addons/test_clientplayback.py:18:1: error[invalid-argument-type] Argument to function `asynccontextmanager` is incorrect: Expected `(...) -> AsyncIterator[Unknown]`, found `def tcp_server(handle_conn, **server_args) -> Unknown | tuple[str, int]`
```


[This](a4d794c59a/test/mitmproxy/addons/test_clientplayback.py (L18-L19))
is a true positive. That function should return
`AsyncIterator[Address]`, not `Address`.

I looked through almost all of the other new diagnostics and they all
look like known problems or true positives.

## Typing conformance

The typing conformance diff looks good.

## Test Plan

New Markdown tests
2025-07-30 11:51:21 +02:00
Andrew Gallant 88a679945c [ty] Add flow diagram for import resolution
The diagram is written in the Dot language, which can
be converted to SVG (or any other image) by GraphViz.

I thought it was a good idea to write this down in
preparation for adding routines that list modules.
Code reuse is likely to be difficult and I wanted to
be sure I understood how it worked.
2025-07-29 14:49:20 -04:00
Andrew Gallant 941be52358 [ty] Add comments to some core resolver functions
Some of the contracts were a little tricky to discover from just the
parameter types, so I added some docs (and fixed what I believe was one
typo).
2025-07-29 14:49:20 -04:00
Andrew Gallant 13624ce17f [ty] Add missing ticks and use consistent quoting
This irked me while I was reading the code, so I just tried to fix what
I could see.
2025-07-29 14:49:20 -04:00
Andrew Gallant edb2f8e997 [ty] Reflow some long lines
I mostly just did this because the long string literals were annoying
me. And these can make rustfmt give up on formatting.

I also re-flowed some long comment lines while I was here.
2025-07-29 14:49:20 -04:00
Andrew Gallant 5e6ad849ff [ty] Unexport helper function
I'm not sure if this used to be used elsewhere, but it no longer is.
And it looks like an internal-only helper function, so just un-export
it.

And note that `ModuleNameIngredient` is also un-exported, so this
function isn't really usable outside of its defining module anyway.
2025-07-29 14:49:20 -04:00
हिमांशु f7c6a6b2d0
[ty] fix a typo (#19621)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-07-29 17:53:44 +00:00
justin 656273bf3d
[ty] synthesize `__replace__` for dataclasses (>=3.13) (#19545)
## Summary
https://github.com/astral-sh/ty/issues/111

adds support for the new `copy.replace` and `__replace__` protocol
[added in 3.13](https://docs.python.org/3/whatsnew/3.13.html#copy)

- docs: https://docs.python.org/3/library/copy.html#object.__replace__
- some discussion on pyright/mypy implementations:
https://discuss.python.org/t/dataclass-transform-and-replace/69067



### Burndown
- [x] add tests
- [x] implement `__replace__`
- [ ]
[collections.namedtuple()](https://docs.python.org/3/library/collections.html#collections.namedtuple)
- [x]
[dataclasses.dataclass](https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass)

## Test Plan
new mdtests

---------

Co-authored-by: David Peter <mail@david-peter.de>
2025-07-29 17:32:01 +02:00
Alex Waygood 81867ea7ce
[ty] Discard `Definition`s when normalizing `Signature`s (#19615) 2025-07-29 14:37:47 +01:00
github-actions[bot] c6a123290d
[ty] Sync vendored typeshed stubs (#19607)
Co-authored-by: typeshedbot <>
Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
2025-07-28 22:06:33 +00:00
Douglas Creager 130d4e1135
[ty] Don't panic with argument that doesn't actually implement Iterable (#19602)
This eliminates the panic reported in
https://github.com/astral-sh/ty/issues/909, though it doesn't address
the underlying cause, which is that we aren't yet checking the types of
the fields of a protocol when checking whether a class implements the
protocol. And in particular, if a class explictly opts out of iteration
via

```py
class NotIterable:
    __iter__ = None
```

we currently treat that as "having an `__iter__`" member, and therefore
implementing `Iterable`.

Note that the assumption that was in the comment before is still
correct: call binding will have already checked that the argument
satisfies `Iterable`, and so it shouldn't be an error to iterate over
said argument. But arguably, the new logic in this PR is a better way to
discharge that assumption — instead of panicking if we happen to be
wrong, fall back on an unknown iteration result.
2025-07-28 12:09:54 -04:00
David Peter 2680f2ed81
[ty] Minor: test isolation (#19597)
## Summary

Split the "Generator functions" tests into two parts. The first part
(synchronous) refers to a function called `i` from a function `i2`. But
`i` is later redeclared in the asynchronous part, which was probably not
intended.
2025-07-28 15:52:59 +02:00
Dylan 008bbfdf5a
Disallow implicit concatenation of t-strings and other string types (#19485)
As of [this cpython PR](https://github.com/python/cpython/pull/135996),
it is not allowed to concatenate t-strings with non-t-strings,
implicitly or explicitly. Expressions such as `"foo" t"{bar}"` are now
syntax errors.

This PR updates some AST nodes and parsing to reflect this change.

The structural change is that `TStringPart` is no longer needed, since,
as in the case of `BytesStringLiteral`, the only possibilities are that
we have a single `TString` or a vector of such (representing an implicit
concatenation of t-strings). This removes a level of nesting from many
AST expressions (which is what all the snapshot changes reflect), and
simplifies some logic in the implementation of visitors, for example.

The other change of note is in the parser. When we meet an implicit
concatenation of string-like literals, we now count the number of
t-string literals. If these do not exhaust the total number of
implicitly concatenated pieces, then we emit a syntax error. To recover
from this syntax error, we encode any t-string pieces as _invalid_
string literals (which means we flag them as invalid, record their
range, and record the value as `""`). Note that if at least one of the
pieces is an f-string we prefer to parse the entire string as an
f-string; otherwise we parse it as a string.

This logic is exactly the same as how we currently treat
`BytesStringLiteral` parsing and error recovery - and carries with it
the same pros and cons.

Finally, note that I have not implemented any changes in the
implementation of the formatter. As far as I can tell, none are needed.
I did change a few of the fixtures so that we are always concatenating
t-strings with t-strings.
2025-07-27 12:41:03 +00:00
Alex Waygood df5eba7583
[ty] Mark `all_type_assignable_to_iterable_are_iterable` as flaky (#19574) 2025-07-27 11:04:13 +00:00
Douglas Creager e867830848
[ty] Don't include already-bound legacy typevars in function generic context (#19558)
We now correctly exclude legacy typevars from enclosing scopes when
constructing the generic context for a generic function.

more detail:

A function is generic if it refers to legacy typevars in its signature:

```py
from typing import TypeVar

T = TypeVar("T")

def f(t: T) -> T:
    return t
```

Generic functions are allowed to appear inside of other generic
contexts. When they do, they can refer to the typevars of those
enclosing generic contexts, and that should not rebind the typevar:

```py
from typing import TypeVar, Generic

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

class C(Generic[T]):
    @staticmethod
    def method(t: T, u: U) -> None: ...

# revealed: def method(t: int, u: U) -> None
reveal_type(C[int].method)
```

This substitution was already being performed correctly, but we were
also still including the enclosing legacy typevars in the method's own
generic context, which can be seen via `ty_extensions.generic_context`
(which has been updated to work on generic functions and methods):

```py
from ty_extensions import generic_context

# before: tuple[T, U]
# after: tuple[U]
reveal_type(generic_context(C[int].method))
```

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-07-25 18:14:19 -04:00
Alex Waygood 859262bd49
[ty] Move `zope.interface` to `good.txt` for primer runs (#19208) 2025-07-25 14:12:17 +01:00
David Peter c0768dfd96
[ty] Attribute access on intersections with negative parts (#19524)
## Summary

We currently infer a `@Todo` type whenever we access an attribute on an
intersection type with negative components. This can happen very
naturally. Consequently, this `@Todo` type is rather pervasive and hides
a lot of true positives that ty could otherwise detect:

```py
class Foo:
    attr: int = 1

def _(f: Foo | None):
    if f:
        reveal_type(f)  # Foo & ~AlwaysFalsy

        reveal_type(f.attr)  # now: int, previously: @Todo
```

The changeset here proposes to handle member access on these
intersection types by simply ignoring all negative contributions. This
is not always ideal: a negative contribution like `~<Protocol with
members 'attr'>` could be a hint that `.attr` should not be accessible
on the full intersection type. The behavior can certainly be improved in
the future, but this seems like a reasonable initial step to get rid of
this unnecessary `@Todo` type.

## Ecosystem analysis

There are quite a few changes here. I spot-checked them and found one
bug where attribute access on pure negation types (`~P == object & ~P`)
would not allow attributes on `object` to be accessed. After that was
fixed, I only see true positives and known problems. The fact that a lot
of `unused-ignore-comment` diagnostics go away are also evidence for the
fact that this touches a sensitive area, where static analysis clashes
with dynamically adding attributes to objects:
```py
… # type: ignore # Runtime attribute access
```

## Test Plan

Updated tests.
2025-07-25 14:56:14 +02:00
David Peter d4eb4277ad
[ty] Add basic support for `dataclasses.field` (#19553)
## Summary

Add basic support for `dataclasses.field`:
* remove fields with `init=False` from the signature of the synthesized
`__init__` method
* infer correct default value types from `default` or `default_factory`
arguments

```py
from dataclasses import dataclass, field

def default_roles() -> list[str]:
    return ["user"]

@dataclass
class Member:
    name: str
    roles: list[str] = field(default_factory=default_roles)
    tag: str | None = field(default=None, init=False)

# revealed: (self: Member, name: str, roles: list[str] = list[str]) -> None
reveal_type(Member.__init__)
```

Support for `kw_only` has **not** been added.

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

## Test Plan

New Markdown tests
2025-07-25 14:56:04 +02:00
Micha Reiser b033fb6bfd
[ty] Split `ScopedPlaceId` into `ScopedSymbolId` and `ScopedMemberId` (#19497) 2025-07-25 13:54:33 +02:00
Alex Waygood f722bfa9e6
[ty] Do not consider a type `T` to satisfy a method member on a protocol unless the method is available on the meta-type of `T` (#19187) 2025-07-25 11:16:04 +01:00
Shunsuke Shibayama b124e182ca
[ty] improve lazy scope place lookup (#19321)
Co-authored-by: David Peter <sharkdp@users.noreply.github.com>
Co-authored-by: Carl Meyer <carl@oddbird.net>
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-07-25 07:11:11 +00:00
Carl Meyer ae9d450b5f
[ty] Fallback to Unknown if no type is stored for an expression (#19517)
## Summary

See discussion at
https://github.com/astral-sh/ruff/pull/19478/files#r2223870292

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

## Test Plan

Added one mdtest for invalid Callable annotation; removed `pull-types:
skip` from that test file.

Co-authored-by: lipefree <willy.ngo.2000@gmail.com>
2025-07-25 02:05:32 +00:00
David Peter d77b7312b0
[ty] Minor: fix incomplete docstring (#19534) 2025-07-24 21:01:15 +02:00
David Peter dc6be457b5
[ty] Support `dataclasses.InitVar` (#19527)
## Summary

I saw that this creates a lot of false positives in the ecosystem, and
it seemed to be relatively easy to add basic support for this.

Some preliminary work on this was done by @InSyncWithFoo — thank you.

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

## Ecosystem analysis

The results look good.

## Test Plan

New Markdown tests

---------

Co-authored-by: InSync <insyncwithfoo@gmail.com>
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-07-24 16:33:33 +02:00
David Peter 9461d3076f
[ty] Rename type_api => ty_extensions (#19523) 2025-07-24 08:24:26 +00:00
Douglas Creager e0149cd9f3
[ty] Return a tuple spec from the iterator protocol (#19496)
This PR updates our iterator protocol machinery to return a tuple spec
describing the elements that are returned, instead of a type. That
allows us to track heterogeneous iterators more precisely, and
consolidates the logic in unpacking and splatting, which are the two
places where we can take advantage of that more precise information.
(Other iterator consumers, like `for` loops, have to collapse the
iterated elements down to a single type regardless, and we provide a new
helper method on `TupleSpec` to perform that summarization.)
2025-07-23 17:11:44 -04:00
David Peter 2a00eca66b
[ty] Exhaustiveness checking & reachability for `match` statements (#19508)
## Summary

Implements proper reachability analysis and — in effect — exhaustiveness
checking for `match` statements. This allows us to check the following
code without any errors (leads to *"can implicitly return `None`"* on
`main`):

```py
from enum import Enum, auto

class Color(Enum):
    RED = auto()
    GREEN = auto()
    BLUE = auto()

def hex(color: Color) -> str:
    match color:
        case Color.RED:
            return "#ff0000"
        case Color.GREEN:
            return "#00ff00"
        case Color.BLUE:
            return "#0000ff"
```

Note that code like this already worked fine if there was a
`assert_never(color)` statement in a catch-all case, because we would
then consider that `assert_never` call terminal. But now this also works
without the wildcard case. Adding a member to the enum would still lead
to an error here, if that case would not be handled in `hex`.

What needed to happen to support this is a new way of evaluating match
pattern constraints. Previously, we would simply compare the type of the
subject expression against the patterns. For the last case here, the
subject type would still be `Color` and the value type would be
`Literal[Color.BLUE]`, so we would infer an ambiguous truthiness.

Now, before we compare the subject type against the pattern, we first
generate a union type that corresponds to the set of all values that
would have *definitely been matched* by previous patterns. Then, we
build a "narrowed" subject type by computing `subject_type &
~already_matched_type`, and compare *that* against the pattern type. For
the example here, `already_matched_type = Literal[Color.RED] |
Literal[Color.GREEN]`, and so we have a narrowed subject type of `Color
& ~(Literal[Color.RED] | Literal[Color.GREEN]) = Literal[Color.BLUE]`,
which allows us to infer a reachability of `AlwaysTrue`.

<details>

<summary>A note on negated reachability constraints</summary>

It might seem that we now perform duplicate work, because we also record
*negated* reachability constraints. But that is still important for
cases like the following (and possibly also for more realistic
scenarios):

```py
from typing import Literal

def _(x: int | str):
    match x:
        case None:
            pass # never reachable
        case _:
            y = 1

    y
```

</details>

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

## Test Plan

* I verified that this solves all examples from the linked ticket (the
first example needs a PEP 695 type alias, because we don't support
legacy type aliases yet)
* Verified that the ecosystem changes are all because of removed false
positives
* Updated tests
2025-07-23 22:45:45 +02:00
David Peter 3d17897c02
[ty] Fix narrowing and reachability of class patterns with arguments (#19512)
## Summary

I noticed that our type narrowing and reachability analysis was
incorrect for class patterns that are not irrefutable. The test cases
below compare the old and the new behavior:

```py
from dataclasses import dataclass

@dataclass
class Point:
    x: int
    y: int

class Other: ...

def _(target: Point):
    y = 1

    match target:
        case Point(0, 0):
            y = 2
        case Point(x=0, y=1):
            y = 3
        case Point(x=1, y=0):
            y = 4
    
    reveal_type(y)  # revealed: Literal[1, 2, 3, 4]    (previously: Literal[2])


def _(target: Point | Other):
    match target:
        case Point(0, 0):
            reveal_type(target)  # revealed: Point
        case Point(x=0, y=1):
            reveal_type(target)  # revealed: Point    (previously: Never)
        case Point(x=1, y=0):
            reveal_type(target)  # revealed: Point    (previously: Never)
        case Other():
            reveal_type(target)  # revealed: Other    (previously: Other & ~Point)
```

## Test Plan

New Markdown test
2025-07-23 18:45:03 +02:00
Jack O'Connor 88bd82938f
[ty] highlight the argument in `static_assert` error messages (#19426)
Closes https://github.com/astral-sh/ty/issues/209.

Before:
```
error[static-assert-error]: Static assertion error: custom message
 --> test.py:2:1
  |
1 | from ty_extensions import static_assert
2 | static_assert(3 > 4, "custom message")
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
```

After:
```
error[static-assert-error]: Static assertion error: custom message
 --> test.py:2:1
  |
1 | from ty_extensions import static_assert
2 | static_assert(3 > 4, "custom message")
  | ^^^^^^^^^^^^^^-----^^^^^^^^^^^^^^^^^^^
  |               |
  |               Inferred type of argument is `Literal[False]`
  |
```
2025-07-23 08:24:12 -07:00
David Peter 5a55bab3f3
[ty] Infer single-valuedness for enums based on `int`/`str` (#19510)
## Summary

We previously didn't recognize `Literal[Color.RED]` as single-valued, if
the enum also derived from `str` or `int`:
```py
from enum import Enum

class Color(str, Enum):
    RED = "red"
    GREEN = "green"
    BLUE = "blue"

def _(color: Color):
    if color == Color.RED:
        reveal_type(color)  # previously: Color, now: Literal[Color.RED]
```

The reason for that was that `int` and `str` have "custom" `__eq__` and
`__ne__` implementations that return `bool`. We do not treat enum
literals from classes with custom `__eq__` and `__ne__` implementations
as single-valued, but of course we know that `int.__eq__` and
`str.__eq__` are well-behaved.

## Test Plan

New Markdown tests.
2025-07-23 15:55:42 +02:00
Andrew Gallant cc5885e564 [ty] Restructure submodule query around `File` dependency
This makes caching of submodules independent of whether `Module`
is itself a Salsa ingredient. In fact, this makes the work done in
the prior commit superfluous. But we're possibly keeping it as an
ingredient for now since it's a bit of a tedious change and we might
need it in the near future.

Ref https://github.com/astral-sh/ruff/pull/19495#pullrequestreview-3045736715
2025-07-23 09:46:40 -04:00
Andrew Gallant 4573a0f6a0 [ty] Make `Module` a Salsa ingredient
We want to write queries that depend on `Module` for caching. While it
seems it can be done without making `Module` an ingredient, it seems it
is best practice to do so.

[best practice to do so]: https://github.com/astral-sh/ruff/pull/19408#discussion_r2215867301
2025-07-23 09:46:40 -04:00
David Peter 905b9d7f51
[ty] Reachability analysis for `isinstance(…)` branches (#19503)
## Summary

Add more precise type inference for a limited set of `isinstance(…)`
calls, i.e. return `Literal[True]` if we can be sure that this is the
correct result. This improves exhaustiveness checking / reachability
analysis for if-elif-else chains with `isinstance` checks. For example:

```py
def is_number(x: int | str) -> bool:  # no "can implicitly return `None` error here anymore
    if isinstance(x, int):
        return True
    elif isinstance(x, str):
        return False

    # code here is now detected as being unreachable
```

This PR also adds a new test suite for exhaustiveness checking.

## Test Plan

New Markdown tests

### Ecosystem analysis

The removed diagnostics look good. There's [one
case](f52c4f1afd/torchvision/io/video_reader.py (L125-L143))
where a "true positive" is removed in unreachable code. `src` is
annotated as being of type `str`, but there is an `elif isinstance(src,
bytes)` branch, which we now detect as unreachable. And so the
diagnostic inside that branch is silenced. I don't think this is a
problem, especially once we have a "graying out" feature, or a lint that
warns about unreachable code.
2025-07-23 13:06:30 +02:00
David Peter b605c3e232
[ty] Normalize single-member enums to their instance type (#19502)
## Summary

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

Labeling this as `internal`, since we haven't released the
enum-expansion feature.

## Test Plan

New Markdown tests
2025-07-23 10:14:20 +02:00
David Peter 385d6fa608
[ty] Detect enums if metaclass is a subtype of EnumType/EnumMeta (#19481)
## Summary

This PR implements the following section from the [typing spec on
enums](https://typing.python.org/en/latest/spec/enums.html#enum-definition):

> Enum classes can also be defined using a subclass of `enum.Enum` **or
any class that uses `enum.EnumType` (or a subclass thereof) as a
metaclass**. Note that `enum.EnumType` was named `enum.EnumMeta` prior
to Python 3.11.

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

## Test Plan

New Markdown tests
2025-07-23 08:46:51 +02:00
Jack O'Connor ba070bb6d5
[ty] perform type narrowing for places marked `global` too (#19381)
Fixes https://github.com/astral-sh/ty/issues/311.
2025-07-22 16:42:10 -07:00
Micha Reiser dc10ab81bd
[ty] Use `ThinVec` for sub segments in `PlaceExpr` (#19470) 2025-07-22 20:39:39 +02:00
Douglas Creager 7673d46b71
[ty] Splat variadic arguments into parameter list (#18996)
This PR updates our call binding logic to handle splatted arguments.

Complicating matters is that we have separated call bind analysis into
two phases: parameter matching and type checking. Parameter matching
looks at the arity of the function signature and call site, and assigns
arguments to parameters. Importantly, we don't yet know the type of each
argument! This is needed so that we can decide whether to infer the type
of each argument as a type form or value form, depending on the
requirements of the parameter that the argument was matched to.

This is an issue when splatting an argument, since we need to know how
many elements the splatted argument contains to know how many positional
parameters to match it against. And to know how many elements the
splatted argument has, we need to know its type.

To get around this, we now make the assumption that splatted arguments
can only be used with value-form parameters. (If you end up splatting an
argument into a type-form parameter, we will silently pass in its
value-form type instead.) That allows us to preemptively infer the
(value-form) type of any splatted argument, so that we have its arity
available during parameter matching. We defer inference of non-splatted
arguments until after parameter matching has finished, as before.

We reuse a lot of the new tuple machinery to make this happen — in
particular resizing the tuple spec representing the number of arguments
passed in with the tuple length representing the number of parameters
the splat was matched with.

This work also shows that we might need to change how we are performing
argument expansion during overload resolution. At the moment, when we
expand parameters, we assume that each argument will still be matched to
the same parameters as before, and only retry the type-checking phase.
With splatted arguments, this is no longer the case, since the inferred
arity of each union element might be different than the arity of the
union as a whole, which can affect how many parameters the splatted
argument is matched to. See the regression test case in
`mdtest/call/function.md` for more details.
2025-07-22 14:33:08 -04:00
David Peter 64e5780037
[ty] Consistent use of American english (in rules) (#19488)
## Summary

Just noticed this as a minor inconsistency in our rules, and had Claude
do a few more automated replacements.
2025-07-22 16:10:38 +02:00
David Peter da8aa6a631
[ty] Support iterating over enums (#19486)
## Summary

Infer the correct type in a scenario like this:

```py
class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3

for color in Color:
    reveal_type(color)  # revealed: Color
```

We should eventually support this out-of-the-box when
https://github.com/astral-sh/ty/issues/501 is implemented. For this
reason, @AlexWaygood would prefer to keep things as they are (we
currently infer `Unknown`, so false positives seem unlikely). But it
seemed relatively easy to support, so I'm opening this for discussion.

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

## Test Plan

Adapted existing test.

## Ecosystem analysis

```diff
- warning[unused-ignore-comment] rotkehlchen/chain/aggregator.py:591:82: Unused blanket `type: ignore` directive
```

This `unused-ignore-comment` goes away due to a new true positive.
2025-07-22 16:09:28 +02:00
David Peter ee69d38000
Fix panic for illegal `Literal[…]` annotations with inner subscript expressions (#19489)
## Summary

Fixes pull-types panics for illegal annotations like
`Literal[object[index]]`.

Originally reported by @AlexWaygood

## Test Plan

* Verified that this caused panics in the playground, when typing (and
potentially hovering over) `x: Literal[obj[0]]`.
* Added a regression test
2025-07-22 14:07:20 +00:00
Brent Westbrook fd335eb8b7
Move fix suggestion to subdiagnostic (#19464)
Summary
--

This PR tweaks Ruff's internal usage of the new diagnostic model to more
closely
match the intended use, as I understand it. Specifically, it moves the
fix/help
suggestion from the primary annotation's message to a subdiagnostic. In
turn, it
adds the secondary/noqa code as the new primary annotation message. As
shown in
the new `ruff_db` tests, this more closely mirrors Ruff's current
diagnostic
output.

I also added `Severity::Help` to render the fix suggestion with a
`help:` prefix
instead of `info:`.

These changes don't have any external impact now but should help a bit
with #19415.

Test Plan
--

New full output format tests in `ruff_db`

Rendered Diagnostics
--

Full diagnostic output from `annotate-snippets` in this PR:

``` 
error[unused-import]: `os` imported but unused
  --> fib.py:1:8
   |
 1 | import os
   |        ^^
   |
 help: Remove unused import: `os`
```

Current Ruff output for the same code:

```
fib.py:1:8: F401 [*] `os` imported but unused
  |
1 | import os
  |        ^^ F401
  |
  = help: Remove unused import: `os`
```

Proposed final output after #19415:

``` 
F401 [*] `os` imported but unused
  --> fib.py:1:8
   |
 1 | import os
   |        ^^
   |
 help: Remove unused import: `os`
```

These are slightly updated from
https://github.com/astral-sh/ruff/pull/19464#issuecomment-3097377634
below to remove the extra noqa codes in the primary annotation messages
for the first and third cases.
2025-07-22 10:03:58 -04:00
Aria Desires c82fa94e0a
[ty] Implement non-stdlib stub mapping for classes and functions (#19471)
This implements mapping of definitions in stubs to definitions in the
"real" implementation using the approach described in
https://github.com/astral-sh/ty/issues/788#issuecomment-3097000287

I've tested this with goto-definition in vscode with code that uses
`colorama` and `types-colorama`.

Notably this implementation does not add support for stub-mapping stdlib
modules, which can be done as an essentially orthogonal followup in the
implementation of `resolve_real_module`.

Part of https://github.com/astral-sh/ty/issues/788
2025-07-22 12:42:55 +00:00
David Peter 6d4687c9af
[ty] Disallow illegal uses of `ClassVar` (#19483)
## Summary

It was faster to implement this then to write the ticket: Disallow
`ClassVar` annotations almost everywhere outside of class body scopes.

## Test Plan

New Markdown tests
2025-07-22 14:21:29 +02:00
David Peter 9180cd094d
[ty] Disallow `Final` in function parameter/return-type annotations (#19480)
## Summary

Disallow `Final` in function parameter- and return-type annotations.

[Typing
spec](https://typing.python.org/en/latest/spec/qualifiers.html#uppercase-final):

> `Final` may only be used in assignments or variable annotations. Using
it in any other position is an error. In particular, `Final` can’t be
used in annotations for function arguments

## Test Plan

Updated MD test
2025-07-22 13:15:19 +02:00
David Peter 9d98a66f65
[ty] Extend `Final` test suite (#19476)
## Summary

Restructures and cleans up the `typing.Final` test suite. Also adds a
few more tests with TODOs based on the [typing spec for
`typing.Final`](https://typing.python.org/en/latest/spec/qualifiers.html#uppercase-final).
2025-07-22 12:06:47 +02:00
David Peter cb60ecef6b
[ty] Minor change to diagnostic message for invalid Literal uses (#19482) 2025-07-22 11:42:12 +02:00
David Peter 215a1c55d4
[ty] Detect illegal non-enum attribute accesses in Literal annotation (#19477)
## Summary

Detect illegal attribute accesses in `Literal[X.Y]` annotations if `X`
is not an enum class.

## Test Plan

New Markdown test
2025-07-22 11:42:03 +02:00
Micha Reiser 5e29278aa2
[ty] Reduce size of `TypeInference` (#19435) 2025-07-22 11:36:36 +02:00
David Peter 30683e3a93 Revert "[ty] Detect illegal non-enum attribute accesses in Literal annotation"
This reverts commit cbc8c08016.
2025-07-22 09:19:44 +02:00
David Peter cbc8c08016 [ty] Detect illegal non-enum attribute accesses in Literal annotation 2025-07-22 09:18:50 +02:00
Alex Waygood cb5a9ff8dc
[ty] Make tuple subclass constructors sound (#19469) 2025-07-21 21:25:11 +00:00
David Peter fcdffe4ac9
[ty] Pass down specialization to generic dataclass bases (#19472)
## Summary

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

## Test Plan

Regression test
2025-07-21 20:51:58 +02:00
Douglas Creager 88de5727df
[ty] Garbage-collect reachability constraints (#19414)
This is a follow-on to #19410 that further reduces the memory usage of
our reachability constraints. When finishing the building of a use-def
map, we walk through all of the "final" states and mark only those
reachability constraints as "used". We then throw away the interior TDD
nodes of any reachability constraints that weren't marked as used.

(This helps because we build up quite a few intermediate TDD nodes when
constructing complex reachability constraints. These nodes can never be
accessed if they were _only_ used as an intermediate TDD node. The
marking step ensures that we keep any nodes that ended up being referred
to in some accessible use-def map state.)
2025-07-21 14:16:27 -04:00
David Peter b8dec79182
[ty] Implicit instance attributes declared `Final` (#19462)
## Summary

Adds proper type inference for implicit instance attributes that are
declared with a "bare" `Final` and adds `invalid-assignment` diagnostics
for all implicit instance attributes that are declared `Final` or
`Final[…]`.

## Test Plan

New and updated MD tests.

## Ecosystem analysis

```diff
pytest (https://github.com/pytest-dev/pytest)
+ error[invalid-return-type] src/_pytest/fixtures.py:1662:24: Return type does not match returned value: expected `Scope`, found `Scope | (Unknown & ~None & ~((...) -> object) & ~str) | (((str, Config, /) -> Unknown) & ~((...) -> object) & ~str) | (Unknown & ~str)
```

The definition of the `scope` attribute is [here](

5f99385635/src/_pytest/fixtures.py (L1020-L1028)).
Looks like this is a new false positive due to missing `TypeAlias`
support that is surfaced here because we now infer a more precise type
for `FixtureDef._scope`.
2025-07-21 20:01:07 +02:00
David Peter dc66019fbc
[ty] Expansion of enums into unions of literals (#19382)
## Summary

Implement expansion of enums into unions of enum literals (and the
reverse operation). For the enum below, this allows us to understand
that `Color = Literal[Color.RED, Color.GREEN, Color.BLUE]`, or that
`Color & ~Literal[Color.RED] = Literal[Color.GREEN, Color.BLUE]`. This
helps in exhaustiveness checking, which is why we see some removed
`assert_never` false positives. And since exhaustiveness checking also
helps with understanding terminal control flow, we also see a few
removed `invalid-return-type` and `possibly-unresolved-reference` false
positives. This PR also adds expansion of enums in overload resolution
and type narrowing constructs.

```py
from enum import Enum
from typing_extensions import Literal, assert_never
from ty_extensions import Intersection, Not, static_assert, is_equivalent_to

class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3

type Red = Literal[Color.RED]
type Green = Literal[Color.GREEN]
type Blue = Literal[Color.BLUE]

static_assert(is_equivalent_to(Red | Green | Blue, Color))
static_assert(is_equivalent_to(Intersection[Color, Not[Red]], Green | Blue))


def color_name(color: Color) -> str:  # no error here (we detect that this can not implicitly return None)
    if color is Color.RED:
        return "Red"
    elif color is Color.GREEN:
        return "Green"
    elif color is Color.BLUE:
        return "Blue"
    else:
        assert_never(color)  # no error here
```

## Performance

I avoided an initial regression here for large enums, but the
`UnionBuilder` and `IntersectionBuilder` parts can certainly still be
optimized. We might want to use the same technique that we also use for
unions of other literals. I didn't see any problems in our benchmarks so
far, so this is not included yet.

## Test Plan

Many new Markdown tests
2025-07-21 19:37:55 +02:00
github-actions[bot] 3785e13231
[ty] Sync vendored typeshed stubs (#19461)
Co-authored-by: typeshedbot <>
Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
2025-07-21 14:01:42 +01:00
Alex Waygood c2380fa0e2
[ty] Extend tuple `__len__` and `__bool__` special casing to also cover tuple subclasses (#19289)
Co-authored-by: Brent Westbrook
2025-07-21 12:50:46 +00:00
David Peter b6579eaf04
[ty] Disallow assignment to `Final` class attributes (#19457)
## Summary

Emit errors for the following assignments:
```py
class C:
    CLASS_LEVEL_CONSTANT: Final[int] = 1

C.CLASS_LEVEL_CONSTANT = 2
C().CLASS_LEVEL_CONSTANT = 2
```

## Test Plan

Updated and new MD tests
2025-07-21 14:27:56 +02:00