diff --git a/crates/ty_python_semantic/resources/mdtest/type_display/callable.md b/crates/ty_python_semantic/resources/mdtest/type_display/callable.md index 907912cfb0..035d822101 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_display/callable.md +++ b/crates/ty_python_semantic/resources/mdtest/type_display/callable.md @@ -1,5 +1,9 @@ # Display of callable types +## Parenthesizing callables + +### Simple + We parenthesize callable types when they appear inside more complex types, to disambiguate: ```py @@ -9,11 +13,13 @@ def f(x: Callable[[], str] | Callable[[int], str]): reveal_type(x) # revealed: (() -> str) | ((int, /) -> str) ``` +### Overloaded + We don't parenthesize display of an overloaded callable, since it is already wrapped in `Overload[...]`: ```py -from typing import overload +from typing import overload, Callable from ty_extensions import CallableTypeOf @overload @@ -28,11 +34,36 @@ def _(flag: bool, c: CallableTypeOf[f]): reveal_type(x) # revealed: Overload[(x: int) -> bool, (x: str) -> str] | Literal[True] ``` +### Top + And we don't parenthesize the top callable, since it is wrapped in `Top[...]`: ```py +from typing import Callable from ty_extensions import Top def f(x: Top[Callable[..., str]] | Callable[[int], int]): reveal_type(x) # revealed: Top[(...) -> str] | ((int, /) -> int) ``` + +## Top ParamSpec + +```toml +[environment] +python-version = "3.12" +``` + +We wrap the signature of a top ParamSpec with `Top[...]`: + +```py +from typing import Callable + +class C[**P]: + def __init__(self, f: Callable[P, object]) -> None: + self.f = f + +def _(x: object): + if callable(x): + c = C(x) + reveal_type(c) # revealed: C[Top[(...)]] +``` diff --git a/crates/ty_python_semantic/src/types/display.rs b/crates/ty_python_semantic/src/types/display.rs index 05360811ce..153d1dc538 100644 --- a/crates/ty_python_semantic/src/types/display.rs +++ b/crates/ty_python_semantic/src/types/display.rs @@ -1600,10 +1600,16 @@ impl<'db> FmtDetailed<'db> for DisplayCallableType<'_, 'db> { match self.signatures.overloads.as_slice() { [signature] => { if matches!(self.kind, CallableTypeKind::ParamSpecValue) { + if signature.parameters().is_top() { + f.write_str("Top[")?; + } signature .parameters() .display_with(self.db, self.settings.clone()) .fmt_detailed(f)?; + if signature.parameters().is_top() { + f.write_str("]")?; + } } else { signature .display_with(self.db, self.settings.clone())