# Cycles ## Function signature Deferred annotations can result in cycles in resolving a function signature: ```py from __future__ import annotations # error: [invalid-type-form] def f(x: f): pass reveal_type(f) # revealed: def f(x: Unknown) -> Unknown ``` ## Unpacking See: ```py class Point: def __init__(self, x: int = 0, y: int = 0) -> None: self.x = x self.y = y def replace_with(self, other: "Point") -> None: self.x, self.y = other.x, other.y p = Point() reveal_type(p.x) # revealed: Unknown | int reveal_type(p.y) # revealed: Unknown | int ``` ## Self-referential bare type alias ```toml [environment] python-version = "3.12" # typing.TypeAliasType ``` ```py from typing import Union, TypeAliasType, Sequence, Mapping A = list["A" | None] def f(x: A): # TODO: should be `list[A | None]`? reveal_type(x) # revealed: list[Divergent] # TODO: should be `A | None`? reveal_type(x[0]) # revealed: Divergent JSONPrimitive = Union[str, int, float, bool, None] JSONValue = TypeAliasType("JSONValue", 'Union[JSONPrimitive, Sequence["JSONValue"], Mapping[str, "JSONValue"]]') def _(x: JSONValue): # TODO: should be `JSONValue` reveal_type(x) # revealed: Divergent ``` ## Self-referential legacy type variables ```py from typing import Generic, TypeVar B = TypeVar("B", bound="Base") class Base(Generic[B]): pass ``` ## Parameter default values This is a regression test for . When a parameter has a default value that references the callable itself, we currently prevent infinite recursion by simply falling back to `Unknown` for the type of the default value, which does not have any practical impact except for the displayed type. We could also consider inferring `Divergent` when we encounter too many layers of nesting (instead of just one), but that would require a type traversal which could have performance implications. So for now, we mainly make sure not to panic or stack overflow for these seeminly rare cases. ### Functions ```py class C: def f(self: "C"): def inner_a(positional=self.a): return self.a = inner_a # revealed: def inner_a(positional=Unknown | (def inner_a(positional=Unknown) -> Unknown)) -> Unknown reveal_type(inner_a) def inner_b(*, kw_only=self.b): return self.b = inner_b # revealed: def inner_b(*, kw_only=Unknown | (def inner_b(*, kw_only=Unknown) -> Unknown)) -> Unknown reveal_type(inner_b) def inner_c(positional_only=self.c, /): return self.c = inner_c # revealed: def inner_c(positional_only=Unknown | (def inner_c(positional_only=Unknown, /) -> Unknown), /) -> Unknown reveal_type(inner_c) def inner_d(*, kw_only=self.d): return self.d = inner_d # revealed: def inner_d(*, kw_only=Unknown | (def inner_d(*, kw_only=Unknown) -> Unknown)) -> Unknown reveal_type(inner_d) ``` We do, however, still check assignability of the default value to the parameter type: ```py class D: def f(self: "D"): # error: [invalid-parameter-default] "Default value of type `Unknown | (def inner_a(a: int = Unknown | (def inner_a(a: int = Unknown) -> Unknown)) -> Unknown)` is not assignable to annotated parameter type `int`" def inner_a(a: int = self.a): ... self.a = inner_a ``` ### Lambdas ```py class C: def f(self: "C"): self.a = lambda positional=self.a: positional self.b = lambda *, kw_only=self.b: kw_only self.c = lambda positional_only=self.c, /: positional_only self.d = lambda *, kw_only=self.d: kw_only # revealed: (positional=Unknown | ((positional=Unknown) -> Unknown)) -> Unknown reveal_type(self.a) # revealed: (*, kw_only=Unknown | ((*, kw_only=Unknown) -> Unknown)) -> Unknown reveal_type(self.b) # revealed: (positional_only=Unknown | ((positional_only=Unknown, /) -> Unknown), /) -> Unknown reveal_type(self.c) # revealed: (*, kw_only=Unknown | ((*, kw_only=Unknown) -> Unknown)) -> Unknown reveal_type(self.d) ``` ## Self-referential implicit attributes ```py class Cyclic: def __init__(self, data: str | dict): self.data = data def update(self): if isinstance(self.data, str): self.data = {"url": self.data} # revealed: Unknown | str | dict[Unknown, Unknown] | dict[Unknown | str, Unknown | str] reveal_type(Cyclic("").data) ```