Back to Ruff

Assignment with annotations

crates/ty_python_semantic/resources/mdtest/assignment/annotations.md

0.15.1224.5 KB
Original Source

Assignment with annotations

Annotation only transparent to local inference

py
x = 1
x: int
y = x

reveal_type(y)  # revealed: Literal[1]

Violates own annotation

py
x: int = "foo"  # error: [invalid-assignment] "Object of type `Literal["foo"]` is not assignable to `int`"

Numbers special case

py
from numbers import Number

# snapshot: invalid-assignment
a: Number = 1
snapshot
error[invalid-assignment]: Object of type `Literal[1]` is not assignable to `Number`
 --> src/mdtest_snippet.py:4:4
  |
4 | a: Number = 1
  |    ------   ^ Incompatible value of type `Literal[1]`
  |    |
  |    Declared type
  |
info: Types from the `numbers` module aren't supported for static type checking
help: Consider using a protocol instead, such as `typing.SupportsFloat`

Violates previous annotation

py
x: int
x = "foo"  # error: [invalid-assignment] "Object of type `Literal["foo"]` is not assignable to `int`"

Tuple annotations are understood

toml
[environment]
python-version = "3.12"

module.py:

py
from typing_extensions import Unpack

a: tuple[()] = ()
b: tuple[int] = (42,)
c: tuple[str, int] = ("42", 42)
d: tuple[tuple[str, str], tuple[int, int]] = (("foo", "foo"), (42, 42))
e: tuple[str, ...] = ()
f: tuple[str, *tuple[int, ...], bytes] = ("42", b"42")
g: tuple[str, Unpack[tuple[int, ...]], bytes] = ("42", b"42")
h: tuple[list[int], list[int]] = ([], [])
i: tuple[str | int, str | int] = (42, 42)
j: tuple[str | int] = (42,)

script.py:

py
from module import a, b, c, d, e, f, g, h, i, j

reveal_type(a)  # revealed: tuple[()]
reveal_type(b)  # revealed: tuple[int]
reveal_type(c)  # revealed: tuple[str, int]
reveal_type(d)  # revealed: tuple[tuple[str, str], tuple[int, int]]
reveal_type(e)  # revealed: tuple[str, ...]

reveal_type(f)  # revealed: tuple[str, *tuple[int, ...], bytes]
reveal_type(g)  # revealed: tuple[str, *tuple[int, ...], bytes]

reveal_type(h)  # revealed: tuple[list[int], list[int]]
reveal_type(i)  # revealed: tuple[str | int, str | int]
reveal_type(j)  # revealed: tuple[str | int]

Incorrect tuple assignments are complained about

py
# error: [invalid-assignment] "Object of type `tuple[Literal[1], Literal[2]]` is not assignable to `tuple[()]`"
a: tuple[()] = (1, 2)

# error: [invalid-assignment] "Object of type `tuple[Literal["foo"]]` is not assignable to `tuple[int]`"
b: tuple[int] = ("foo",)

# error: [invalid-assignment]
c: tuple[str | int, str] = ([], "foo")

Collection literal annotations are understood

toml
[environment]
python-version = "3.12"
py
import typing

a: list[int] = [1, 2, 3]
reveal_type(a)  # revealed: list[int]

b: list[int | str] = [1, 2, 3]
reveal_type(b)  # revealed: list[int | str]

c: typing.List[int] = [1, 2, 3]
reveal_type(c)  # revealed: list[int]

d: list[typing.Any] = []
reveal_type(d)  # revealed: list[Any]

e: set[int] = {1, 2, 3}
reveal_type(e)  # revealed: set[int]

f: set[int | str] = {1, 2, 3}
reveal_type(f)  # revealed: set[int | str]

g: typing.Set[int] = {1, 2, 3}
reveal_type(g)  # revealed: set[int]

h: list[list[int]] = [[], [42]]
reveal_type(h)  # revealed: list[list[int]]

i: list[typing.Any] = [1, 2, "3", ([4],)]
reveal_type(i)  # revealed: list[Any]

j: list[tuple[str | int, ...]] = [(1, 2), ("foo", "bar"), ()]
reveal_type(j)  # revealed: list[tuple[str | int, ...]]

k: list[tuple[list[int], ...]] = [([],), ([1, 2], [3, 4]), ([5], [6], [7])]
reveal_type(k)  # revealed: list[tuple[list[int], ...]]

l: tuple[list[int], *tuple[list[typing.Any], ...], list[str]] = ([1, 2, 3], [4, 5, 6], [7, 8, 9], ["10", "11", "12"])
reveal_type(l)  # revealed: tuple[list[int], list[Any], list[Any], list[str]]

type IntList = list[int]

m: IntList = [1, 2, 3]
reveal_type(m)  # revealed: list[int]

n: list[typing.Literal[1, 2, 3]] = [1, 2, 3]
reveal_type(n)  # revealed: list[Literal[1, 2, 3]]

o: list[typing.LiteralString] = ["a", "b", "c"]
reveal_type(o)  # revealed: list[LiteralString]

p: dict[int, int] = {}
reveal_type(p)  # revealed: dict[int, int]

q: dict[int | str, int] = {1: 1, 2: 2, 3: 3}
reveal_type(q)  # revealed: dict[int | str, int]

r: dict[int | str, int | str] = {1: 1, 2: 2, 3: 3}
reveal_type(r)  # revealed: dict[int | str, int | str]

s: dict[int | str, int | str]
s = {1: 1, 2: 2, 3: 3}
reveal_type(s)  # revealed: dict[int | str, int | str]
(s := {1: 1, 2: 2, 3: 3})
reveal_type(s)  # revealed: dict[int | str, int | str]

Optional collection literal annotations are understood

toml
[environment]
python-version = "3.12"
py
import typing

a: list[int] | None = [1, 2, 3]
reveal_type(a)  # revealed: list[int]

b: list[int | str] | None = [1, 2, 3]
reveal_type(b)  # revealed: list[int | str]

c: typing.List[int] | None = [1, 2, 3]
reveal_type(c)  # revealed: list[int]

d: list[typing.Any] | None = []
reveal_type(d)  # revealed: list[Any]

e: set[int] | None = {1, 2, 3}
reveal_type(e)  # revealed: set[int]

f: set[int | str] | None = {1, 2, 3}
reveal_type(f)  # revealed: set[int | str]

g: typing.Set[int] | None = {1, 2, 3}
reveal_type(g)  # revealed: set[int]

h: list[list[int]] | None = [[], [42]]
reveal_type(h)  # revealed: list[list[int]]

i: list[typing.Any] | None = [1, 2, "3", ([4],)]
reveal_type(i)  # revealed: list[Any]

j: list[tuple[str | int, ...]] | None = [(1, 2), ("foo", "bar"), ()]
reveal_type(j)  # revealed: list[tuple[str | int, ...]]

k: list[tuple[list[int], ...]] | None = [([],), ([1, 2], [3, 4]), ([5], [6], [7])]
reveal_type(k)  # revealed: list[tuple[list[int], ...]]

l: tuple[list[int], *tuple[list[typing.Any], ...], list[str]] | None = ([1, 2, 3], [4, 5, 6], [7, 8, 9], ["10", "11", "12"])
reveal_type(l)  # revealed: tuple[list[int], list[Any], list[Any], list[str]]

type IntList = list[int]

m: IntList | None = [1, 2, 3]
reveal_type(m)  # revealed: list[int]

n: list[typing.Literal[1, 2, 3]] | None = [1, 2, 3]
reveal_type(n)  # revealed: list[Literal[1, 2, 3]]

o: list[typing.LiteralString] | None = ["a", "b", "c"]
reveal_type(o)  # revealed: list[LiteralString]

p: dict[int, int] | None = {}
reveal_type(p)  # revealed: dict[int, int]

q: dict[int | str, int] | None = {1: 1, 2: 2, 3: 3}
reveal_type(q)  # revealed: dict[int | str, int]

r: dict[int | str, int | str] | None = {1: 1, 2: 2, 3: 3}
reveal_type(r)  # revealed: dict[int | str, int | str]

Incorrect collection literal assignments are complained about

py
# error: [invalid-assignment] "Object of type `list[str | int]` is not assignable to `list[str]`"
a: list[str] = [1, 2, 3]

# error: [invalid-assignment] "Object of type `set[int | str]` is not assignable to `set[int]`"
b: set[int] = {1, 2, "3"}

Generic constructor annotations are understood

toml
[environment]
python-version = "3.12"
py
from typing import Any

class X[T]:
    def __init__(self, value: T):
        self.value = value

x1: X[int] = X(1)
reveal_type(x1)  # revealed: X[int]

x2: X[int | None] = X(1)
reveal_type(x2)  # revealed: X[int | None]

x3: X[int | None] | None = X(1)
reveal_type(x3)  # revealed: X[int | None]

def _[T](x1: X[T]):
    x2: X[T | int] = X(x1.value)
    reveal_type(x2)  # revealed: X[T@_ | int]

x4: X[Any] = X(1)
reveal_type(x4)  # revealed: X[Any]

def _(flag: bool):
    x5: X[int | None] = X(1) if flag else X(2)
    reveal_type(x5)  # revealed: X[int | None]
py
from dataclasses import dataclass

@dataclass
class Y[T]:
    value: T

y1 = Y(value=1)
reveal_type(y1)  # revealed: Y[int]

y2: Y[Any] = Y(value=1)
reveal_type(y2)  # revealed: Y[Any]
py
class Z[T]:
    value: T

    def __new__(cls, value: T):
        return super().__new__(cls)

z1 = Z(1)
reveal_type(z1)  # revealed: Z[int]

z2: Z[Any] = Z(1)
reveal_type(z2)  # revealed: Z[Any]

PEP-604 annotations are supported

py
def foo(v: str | int | None, w: str | str | None, x: str | str):
    reveal_type(v)  # revealed: str | int | None
    reveal_type(w)  # revealed: str | None
    reveal_type(x)  # revealed: str

PEP-604 in non-type-expression context

In Python 3.10 and later

toml
[environment]
python-version = "3.10"
py
IntOrStr = int | str

Earlier versions

toml
[environment]
python-version = "3.9"
py
# snapshot: unsupported-operator
IntOrStr = int | str
snapshot
error[unsupported-operator]: Unsupported `|` operation
 --> src/mdtest_snippet.py:2:12
  |
2 | IntOrStr = int | str
  |            ---^^^---
  |            |     |
  |            |     Has type `<class 'str'>`
  |            Has type `<class 'int'>`
  |
info: PEP 604 `|` unions are only available on Python 3.10+ unless they are quoted
info: Python 3.9 was assumed when resolving types because it was specified on the command line

Attribute expressions in type annotations are understood

py
import builtins

int = "foo"
a: builtins.int = 42

# error: [invalid-assignment] "Object of type `Literal["bar"]` is not assignable to `int`"
b: builtins.int = "bar"

c: builtins.tuple[builtins.tuple[builtins.int, builtins.int], builtins.int] = ((42, 42), 42)

# error: [invalid-assignment] "Object of type `Literal["foo"]` is not assignable to `tuple[tuple[int, int], int]`"
c: builtins.tuple[builtins.tuple[builtins.int, builtins.int], builtins.int] = "foo"

Future annotations are deferred

py
from __future__ import annotations

x: Foo

class Foo: ...

x = Foo()
reveal_type(x)  # revealed: Foo

Annotations in stub files are deferred

pyi
x: Foo

class Foo: ...

x = Foo()
reveal_type(x)  # revealed: Foo

Annotations are deferred by default in Python 3.14 and later

toml
[environment]
python-version = "3.14"
py
x: Foo

class Foo: ...

x = Foo()
reveal_type(x)  # revealed: Foo

Annotated assignments in stub files are inferred correctly

pyi
x: int = 1
reveal_type(x)  # revealed: Literal[1]

Annotations influence generic call inference

toml
[environment]
python-version = "3.12"
py
from typing import Literal, Sequence

def f[T](x: T) -> list[T]:
    return [x]

x1 = f("a")
reveal_type(x1)  # revealed: list[str]

x2: list[int | Literal["a"]] = f("a")
reveal_type(x2)  # revealed: list[int | Literal["a"]]

x3: list[int | str] = f("a")
reveal_type(x3)  # revealed: list[int | str]

x4: list[int | tuple[int, int]] = f((1, 2))
reveal_type(x4)  # revealed: list[int | tuple[int, int]]

x5: list[int] = f(True)
reveal_type(x5)  # revealed: list[int]

# error: [invalid-assignment] "Object of type `list[str]` is not assignable to `list[int]`"
x6: list[int] = f("a")

# error: [invalid-assignment] "Object of type `list[str]` is not assignable to `tuple[int]`"
x7: tuple[int] = f("a")

def f2[T: int](x: T) -> T:
    return x

x8: int = f2(True)
reveal_type(x8)  # revealed: Literal[True]

x9: int | str = f2(True)
reveal_type(x9)  # revealed: Literal[True]

x10: list[int | str] | list[int | None] = [1, 2, 3]
reveal_type(x10)  # revealed: list[int | str]

x11: Sequence[int | str] | Sequence[int | None] = [1, 2, 3]
reveal_type(x11)  # revealed: list[int]

x12: list[int] | list[int | None] | list[str | None] = ["1", "2"]
reveal_type(x12)  # revealed: list[str | None]

x13: dict[str, list[int | None]] | dict[str, list[str | None]] = {"a": ["b"]}
reveal_type(x13)  # revealed: dict[str, list[str | None]]

x14 = [{"a": [1], "b": 1}, {"a": [1]}]
x14.append(reveal_type({"b": 1}))  # revealed: dict[str, list[int] | int]
reveal_type(x14)  # revealed: list[dict[str, list[int] | int] | dict[str, list[int]]]

Annotations influence generic call argument inference

toml
[environment]
python-version = "3.13"

A function's arguments are also inferred using the type context:

py
from typing import TypedDict

class TD(TypedDict):
    x: int

def first[T](x: list[T]) -> T:
    return x[0]

x1: TD = first([{"x": 0}, {"x": 1}])
reveal_type(x1)  # revealed: TD

x2: TD | None = first([{"x": 0}, {"x": 1}])
reveal_type(x2)  # revealed: TD

# error: [missing-typed-dict-key] "Missing required key 'x' in TypedDict `TD` constructor"
# error: [invalid-key] "Unknown key "y" for TypedDict `TD`"
# error: [invalid-assignment] "Object of type `TD | dict[str, int]` is not assignable to `TD`"
x3: TD = first([{"y": 0}, {"x": 1}])

# error: [missing-typed-dict-key] "Missing required key 'x' in TypedDict `TD` constructor"
# error: [invalid-key] "Unknown key "y" for TypedDict `TD`"
# error: [invalid-assignment] "Object of type `TD | None | dict[str, int]` is not assignable to `TD | None`"
x4: TD | None = first([{"y": 0}, {"x": 1}])

But not in a way that leads to assignability errors:

py
from typing import TypedDict, Any

class TD2(TypedDict):
    x: str

def _(dt: dict[str, Any], key: str):
    x1: TD = dt.get(key, {})
    reveal_type(x1)  # revealed: Any

    x2: TD = dt.get(key, {"x": 0})
    reveal_type(x2)  # revealed: Any

    x3: TD | None = dt.get(key, {})
    reveal_type(x3)  # revealed: Any

    x4: TD | None = dt.get(key, {"x": 0})
    reveal_type(x4)  # revealed: Any

    x5: TD2 = dt.get(key, {})
    reveal_type(x5)  # revealed: Any

    x6: TD2 = dt.get(key, {"x": 0})
    reveal_type(x6)  # revealed: Any

    x7: TD2 | None = dt.get(key, {})
    reveal_type(x7)  # revealed: Any

    x8: TD2 | None = dt.get(key, {"x": 0})
    reveal_type(x8)  # revealed: Any

Partially specialized type context is not ignored:

py
from typing import TypeVar

U = TypeVar("U", default=Any)

class X: ...

def lst[T](x: T) -> list[T]:
    return [x]

def two_lists[T](x: list[T | int], y: list[T | str]) -> T:
    raise NotImplementedError

def two_lists_default(x: list[U | int], y: list[U | str]) -> U:
    raise NotImplementedError

def dct[K, V](k: K, v: V) -> dict[K, V]:
    return {k: v}

def two_dicts[T](x: dict[T | int, Any], y: dict[T | str, Any]) -> T:
    raise NotImplementedError

def two_dicts_default(x: dict[U | int, Any], y: dict[U | str, Any]) -> U:
    raise NotImplementedError

def _():
    # revealed: list[int | X]
    # revealed: list[str | X]
    x1 = two_lists(reveal_type(lst(X())), reveal_type(lst(X())))
    reveal_type(x1)  # revealed: X

    # revealed: list[int | X]
    # revealed: list[str | X]
    x2 = two_lists_default(reveal_type(lst(X())), reveal_type(lst(X())))
    reveal_type(x2)  # revealed: X

    # revealed: dict[int | X, Any]
    # revealed: dict[str | X, Any]
    x3 = two_dicts(reveal_type(dct(X(), X())), reveal_type(dct(X(), X())))
    reveal_type(x3)  # revealed: X

    # revealed: dict[int | X, Any]
    # revealed: dict[str | X, Any]
    x4 = two_dicts_default(reveal_type(dct(X(), X())), reveal_type(dct(X(), X())))
    reveal_type(x4)  # revealed: X

Prefer the declared type of generic classes and callables

toml
[environment]
python-version = "3.14"
py
from typing import Any

def f[T](x: T) -> list[T]:
    return [x]

def f2[T](x: T) -> list[T] | None:
    return [x]

def f3[T](x: T) -> list[T] | dict[T, T]:
    return [x]

a = f(1)
reveal_type(a)  # revealed: list[int]

b: list[Any] = f(1)
reveal_type(b)  # revealed: list[Any]

c: list[Any] = [1]
reveal_type(c)  # revealed: list[Any]

d: list[Any] | None = f(1)
reveal_type(d)  # revealed: list[Any]

e: list[Any] | None = [1]
reveal_type(e)  # revealed: list[Any]

f: list[Any] | None = f2(1)
reveal_type(f)  # revealed: list[Any] | None

g: list[Any] | dict[Any, Any] = f3(1)
# TODO: Better constraint solver.
reveal_type(g)  # revealed: list[int] | dict[int, int]

We only prefer the declared type if it is in non-covariant position.

py
class Bivariant[T]:
    pass

class Covariant[T]:
    def pop(self) -> T:
        raise NotImplementedError

class Contravariant[T]:
    def push(self, value: T) -> None:
        pass

class Invariant[T]:
    x: T

def bivariant[T](x: T) -> Bivariant[T]:
    return Bivariant()

def covariant[T](x: T) -> Covariant[T]:
    return Covariant()

def contravariant[T](x: T) -> Contravariant[T]:
    return Contravariant()

def invariant[T](x: T) -> Invariant[T]:
    return Invariant()

x1 = bivariant(1)
x2 = covariant(1)
x3 = contravariant(1)
x4 = invariant(1)

reveal_type(x1)  # revealed: Bivariant[Literal[1]]
reveal_type(x2)  # revealed: Covariant[Literal[1]]
reveal_type(x3)  # revealed: Contravariant[int]
reveal_type(x4)  # revealed: Invariant[int]

x5: Bivariant[Any] = bivariant(1)
x6: Covariant[Any] = covariant(1)
x7: Contravariant[Any] = contravariant(1)
x8: Invariant[Any] = invariant(1)

reveal_type(x5)  # revealed: Bivariant[Literal[1]]
reveal_type(x6)  # revealed: Covariant[Literal[1]]
reveal_type(x7)  # revealed: Contravariant[Any]
reveal_type(x8)  # revealed: Invariant[Any]
py
class X[T]:
    def __init__(self: X[None]): ...
    def pop(self) -> T:
        raise NotImplementedError

x1: X[int | None] = X()
reveal_type(x1)  # revealed: X[None]

We also prefer the declared type of Callable parameters, which are in contravariant position:

py
from typing import Callable

type AnyToBool = Callable[[Any], bool]

def wrap[**P, T](f: Callable[P, T]) -> Callable[P, T]:
    return f

def make_callable[T](x: T) -> Callable[[T], bool]:
    raise NotImplementedError

def maybe_make_callable[T](x: T) -> Callable[[T], bool] | None:
    raise NotImplementedError

x1: Callable[[Any], bool] = make_callable(0)
reveal_type(x1)  # revealed: (Any, /) -> bool

x2: AnyToBool = make_callable(0)
reveal_type(x2)  # revealed: (Any, /) -> bool

x3: Callable[[list[Any]], bool] = make_callable([0])
reveal_type(x3)  # revealed: (list[Any], /) -> bool

x4: Callable[[Any], bool] = wrap(make_callable(0))
reveal_type(x4)  # revealed: (Any, /) -> bool

x5: Callable[[Any], bool] | None = maybe_make_callable(0)
reveal_type(x5)  # revealed: ((Any, /) -> bool) | None

Declared type preference sees through subtyping

toml
[environment]
python-version = "3.12"

Similarly, if the inferred type is a subtype of the declared type, we prefer declared type assignments that are in non-covariant position.

py
from collections import defaultdict
from typing import Any, Iterable, Literal, MutableSequence, Sequence

x1: Sequence[Any] = [1, 2, 3]
reveal_type(x1)  # revealed: list[int]

x2: MutableSequence[Any] = [1, 2, 3]
reveal_type(x2)  # revealed: list[Any]

x3: Iterable[Any] = [1, 2, 3]
reveal_type(x3)  # revealed: list[int]

x4: Iterable[Iterable[Any]] = [[1, 2, 3]]
reveal_type(x4)  # revealed: list[list[int]]

x5: list[Iterable[Any]] = [[1, 2, 3]]
reveal_type(x5)  # revealed: list[Iterable[Any]]

x6: Iterable[list[Any]] = [[1, 2, 3]]
reveal_type(x6)  # revealed: list[list[Any]]

x7: Sequence[Any] = [i for i in [1, 2, 3]]
reveal_type(x7)  # revealed: list[int]

x8: MutableSequence[Any] = [i for i in [1, 2, 3]]
reveal_type(x8)  # revealed: list[Any]

x9: Iterable[Any] = [i for i in [1, 2, 3]]
reveal_type(x9)  # revealed: list[int]

x10: Iterable[Iterable[Any]] = [[i] for i in [1, 2, 3]]
reveal_type(x10)  # revealed: list[list[int]]

x11: list[Iterable[Any]] = [[i] for i in [1, 2, 3]]
reveal_type(x11)  # revealed: list[Iterable[Any]]

x12: Iterable[list[Any]] = [[i] for i in [1, 2, 3]]
reveal_type(x12)  # revealed: list[list[Any]]

class X[T]:
    value: T

    def __init__(self, value: T): ...

class A[T](X[T]): ...

def a[T](value: T) -> A[T]:
    return A(value)

x13: A[object] = A(1)
reveal_type(x13)  # revealed: A[object]

x14: X[object] = A(1)
reveal_type(x14)  # revealed: A[object]

x15: X[object] | None = A(1)
reveal_type(x15)  # revealed: A[object]

x16: X[object] | None = a(1)
reveal_type(x16)  # revealed: A[object]

def f[T](x: T) -> list[list[T]]:
    return [[x]]

x17: Sequence[Sequence[Any]] = f(1)
reveal_type(x17)  # revealed: list[list[int]]

x18: Sequence[list[Any]] = f(1)
reveal_type(x18)  # revealed: list[list[Any]]

x19: dict[int, dict[str, int]] = defaultdict(dict)
reveal_type(x19)  # revealed: defaultdict[int, dict[str, int]]

Narrow generic unions

toml
[environment]
python-version = "3.12"
py
from typing import reveal_type, Any, Callable, TypedDict

def identity[T](x: T) -> T:
    return x

type Target = Any | list[str] | dict[str, str] | Callable[[str], None] | None

def _(narrow: dict[str, str], target: Target):
    target = identity(narrow)
    reveal_type(target)  # revealed: dict[str, str]

def _(narrow: list[str], target: Target):
    target = identity(narrow)
    reveal_type(target)  # revealed: list[str]

def _(narrow: Callable[[str], None], target: Target):
    target = identity(narrow)
    reveal_type(target)  # revealed: (str, /) -> None

def _(narrow: list[str] | dict[str, str], target: Target):
    target = identity(narrow)
    reveal_type(target)  # revealed: list[str] | dict[str, str]

class TD(TypedDict):
    x: int

type TargetWithTD = Any | list[TD] | dict[str, TD] | Callable[[TD], None] | None

def _(target: TargetWithTD):
    target = identity([{"x": 1}])
    reveal_type(target)  # revealed: list[TD]

def _(target: TargetWithTD):
    target = identity({"x": {"x": 1}})
    reveal_type(target)  # revealed: dict[str, TD]

def _(target: TargetWithTD):
    def make_callable[T](x: T) -> Callable[[T], None]:
        raise NotImplementedError

    target = identity(make_callable({"x": 1}))
    reveal_type(target)  # revealed: (TD, /) -> None

Prefer the inferred type of non-generic classes

toml
[environment]
python-version = "3.12"
py
def identity[T](x: T) -> T:
    return x

def lst[T](x: T) -> list[T]:
    return [x]

def _(i: int):
    a: int | None = i
    b: int | None = identity(i)
    c: int | str | None = identity(i)
    reveal_type(a)  # revealed: int
    reveal_type(b)  # revealed: int
    reveal_type(c)  # revealed: int

    a: list[int | None] | None = [i]
    b: list[int | None] | None = identity([i])
    c: list[int | None] | int | None = identity([i])
    reveal_type(a)  # revealed: list[int | None]
    reveal_type(b)  # revealed: list[int | None]
    reveal_type(c)  # revealed: list[int | None]

    a: list[int | None] | None = [i]
    b: list[int | None] | None = lst(i)
    c: list[int | None] | int | None = lst(i)
    reveal_type(a)  # revealed: list[int | None]
    reveal_type(b)  # revealed: list[int | None]
    reveal_type(c)  # revealed: list[int | None]

    a: list | None = []
    b: list | None = identity([])
    c: list | int | None = identity([])
    reveal_type(a)  # revealed: list[Unknown]
    reveal_type(b)  # revealed: list[Unknown]
    reveal_type(c)  # revealed: list[Unknown]

def f[T](x: list[T]) -> T:
    return x[0]

def _(a: int, b: str, c: int | str):
    x1: int = f(lst(a))
    reveal_type(x1)  # revealed: int

    x2: int | str = f(lst(a))
    reveal_type(x2)  # revealed: int

    x3: int | None = f(lst(a))
    reveal_type(x3)  # revealed: int

    x4: str = f(lst(b))
    reveal_type(x4)  # revealed: str

    x5: int | str = f(lst(b))
    reveal_type(x5)  # revealed: str

    x6: str | int = f(lst(b))
    reveal_type(x6)  # revealed: str

    x7: str | None = f(lst(b))
    reveal_type(x7)  # revealed: str

    x8: int | str = f(lst(c))
    reveal_type(x8)  # revealed: int | str

    x9: int | str = f(lst(c))
    reveal_type(x9)  # revealed: int | str

    # TODO: Ideally this would reveal `int | str`. This is a known limitation of our
    # call inference solver, and would require an extra inference attempt without type
    # context, or with type context of subsets of the union, both of which are impractical
    # for performance reasons.
    x10: int | str | None = f(lst(c))
    reveal_type(x10)  # revealed: int | str | None

Assignability diagnostics ignore declared type

toml
[environment]
python-version = "3.12"

The type displayed in an invalid assignment diagnostic should account for the type context, e.g., to avoid literal promotion:

py
from typing import Literal, TypedDict

def f[T](x: T) -> list[T]:
    return [x]

# error: [invalid-assignment] "Object of type `list[Literal["hello"] | int]` is not assignable to `list[Literal["hello"] | bool]`"
x1: list[Literal["hello"] | bool] = ["hello", 1]

class A(TypedDict):
    bar: int

# error: [invalid-assignment] "Object of type `list[A | int]` is not assignable to `list[A | bool]`"
x2: list[A | bool] = [{"bar": 1}, 1]

However, the declared type should be ignored if the specialization is not solvable:

py
from typing import Any, Callable

def g[T](x: list[T]) -> T:
    return x[0]

def _(a: int | None):
    # error: [invalid-assignment] "Object of type `list[int | None]` is not assignable to `list[str]`"
    x1: list[str] = f(a)

    # error: [invalid-assignment] "Object of type `int | None` is not assignable to `str`"
    x2: str = g(f(a))

def make_callable[T](x: T) -> Callable[[T], bool]:
    raise NotImplementedError

def _(a: int | None):
    # error: [invalid-assignment] "Object of type `(int | None, /) -> bool` is not assignable to `(str, /) -> bool`"
    x1: Callable[[str], bool] = make_callable(a)

Forward annotation with unclosed string literal

Regression test for #1611.

<!-- fmt:off -->
py
# error: [invalid-syntax]
# error: [invalid-syntax-in-forward-annotation]
a:'
<!-- fmt:on -->