Commit Graph

22 Commits

Author SHA1 Message Date
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
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
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
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
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
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
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
Ibraheem Ahmed bea92c8229
[ty] More precise type inference for dictionary literals (#20523)
## Summary

Extends https://github.com/astral-sh/ruff/pull/20360 to dictionary
literals. This also improves our `TypeDict` support by passing through
nested type context.
2025-09-24 18:12:00 -04:00
Ibraheem Ahmed 12086dfa69 re-infer RHS of annotated assignments in isolation for assignability diagnostics 2025-09-19 17:00:37 -04:00
Ibraheem Ahmed 5f294f9f2e use type context for inference of generic function calls 2025-09-19 17:00:37 -04:00
Ibraheem Ahmed e84d523bcf
[ty] Infer more precise types for collection literals (#20360)
## Summary

Part of https://github.com/astral-sh/ty/issues/168. Infer more precise types for collection literals (currently, only `list` and `set`). For example,

```py
x = [1, 2, 3] # revealed: list[Unknown | int]
y: list[int] = [1, 2, 3] # revealed: list[int]
```

This could easily be extended to `dict` literals, but I am intentionally limiting scope for now.
2025-09-17 18:51:50 -04:00
Alex Waygood 7d0c8e045c
[ty] Infer slightly more precise types for comprehensions (#20111) 2025-08-27 13:21:47 +01: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
Alex Waygood 7b4103bcb6
[ty] Remove special casing for tuple addition (#19636) 2025-07-30 16:25:42 +00:00
Douglas Creager ea812d0813
[ty] Homogeneous and mixed tuples (#18600)
We already had support for homogeneous tuples (`tuple[int, ...]`). This
PR extends this to also support mixed tuples (`tuple[str, str,
*tuple[int, ...], str str]`).

A mixed tuple consists of a fixed-length (possibly empty) prefix and
suffix, and a variable-length portion in the middle. Every element of
the variable-length portion must be of the same type. A homogeneous
tuple is then just a mixed tuple with an empty prefix and suffix.

The new data representation uses different Rust types for a fixed-length
(aka heterogeneous) tuple. Another option would have been to use the
`VariableLengthTuple` representation for all tuples, and to wrap the
"variable + suffix" portion in an `Option`. I don't think that would
simplify the method implementations much, though, since we would still
have a 2×2 case analysis for most of them.

One wrinkle is that the definition of the `tuple` class in the typeshed
has a single typevar, and canonically represents a homogeneous tuple.
When getting the class of a tuple instance, that means that we have to
summarize our detailed mixed tuple type information into its
"homogeneous supertype". (We were already doing this for heterogeneous
types.)

A similar thing happens when concatenating two mixed tuples: the
variable-length portion and suffix of the LHS, and the prefix and
variable-length portion of the RHS, all get unioned into the
variable-length portion of the result. The LHS prefix and RHS suffix
carry through unchanged.

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-06-20 18:23:54 -04:00
David Peter 0ede831a3f
[ty] Add hint that PEP 604 union syntax is only available in 3.10+ (#18192)
## Summary

Add a new diagnostic hint if you try to use PEP 604 `X | Y` union syntax
in a non-type-expression before 3.10.

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

## Test Plan

New snapshot test
2025-05-19 19:47:31 +02:00
Alex Waygood 55df9271ba
[ty] Understand homogeneous tuple annotations (#17998) 2025-05-12 22:02:25 -04:00
Alex Waygood d1bb10a66b
[ty] Understand classes that inherit from subscripted `Protocol[]` as generic (#17832) 2025-05-09 17:39:15 +01:00
Micha Reiser b51c4f82ea
Rename Red Knot (#17820) 2025-05-03 19:49:15 +02:00