diff --git a/crates/red_knot_ide/src/hover.rs b/crates/red_knot_ide/src/hover.rs index e8f5e17b6b..dfe3f780e1 100644 --- a/crates/red_knot_ide/src/hover.rs +++ b/crates/red_knot_ide/src/hover.rs @@ -214,11 +214,11 @@ mod tests { "#, ); - assert_snapshot!(test.hover(), @r" - Literal[foo] + assert_snapshot!(test.hover(), @r###" + def foo(a, b) -> Unknown --------------------------------------------- ```text - Literal[foo] + def foo(a, b) -> Unknown ``` --------------------------------------------- info: lint:hover: Hovered content is @@ -231,7 +231,7 @@ mod tests { | | | source | - "); + "###); } #[test] @@ -312,11 +312,11 @@ mod tests { "#, ); - assert_snapshot!(test.hover(), @r" - Literal[foo, bar] + assert_snapshot!(test.hover(), @r###" + (def foo(a, b) -> Unknown) | (def bar(a, b) -> Unknown) --------------------------------------------- ```text - Literal[foo, bar] + (def foo(a, b) -> Unknown) | (def bar(a, b) -> Unknown) ``` --------------------------------------------- info: lint:hover: Hovered content is @@ -329,7 +329,7 @@ mod tests { | | | source | - "); + "###); } #[test] diff --git a/crates/red_knot_python_semantic/resources/mdtest/annotations/callable.md b/crates/red_knot_python_semantic/resources/mdtest/annotations/callable.md index e17cb1cbf3..06388bb72f 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/annotations/callable.md +++ b/crates/red_knot_python_semantic/resources/mdtest/annotations/callable.md @@ -289,7 +289,7 @@ def _(c: Callable[[int, Unpack[Ts]], int]): from typing import Callable def _(c: Callable[[int], int]): - reveal_type(c.__init__) # revealed: Literal[__init__] + reveal_type(c.__init__) # revealed: def __init__(self) -> None reveal_type(c.__class__) # revealed: type # TODO: The member lookup for `Callable` uses `object` which does not have a `__call__` diff --git a/crates/red_knot_python_semantic/resources/mdtest/attributes.md b/crates/red_knot_python_semantic/resources/mdtest/attributes.md index 14761c79e9..6de5cc7fdb 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/attributes.md +++ b/crates/red_knot_python_semantic/resources/mdtest/attributes.md @@ -1373,7 +1373,7 @@ from typing import Any class Foo(Any): ... reveal_type(Foo.bar) # revealed: Any -reveal_type(Foo.__repr__) # revealed: Literal[__repr__] & Any +reveal_type(Foo.__repr__) # revealed: (def __repr__(self) -> str) & Any ``` Similar principles apply if `Any` appears in the middle of an inheritance hierarchy: @@ -1672,7 +1672,7 @@ Some attributes are special-cased, however: ```py reveal_type(f.__get__) # revealed: -reveal_type(f.__call__) # revealed: +reveal_type(f.__call__) # revealed: ``` ### Int-literal attributes @@ -1681,7 +1681,7 @@ Most attribute accesses on int-literal types are delegated to `builtins.int`, si integers are instances of that class: ```py -reveal_type((2).bit_length) # revealed: +reveal_type((2).bit_length) # revealed: bound method Literal[2].bit_length() -> int reveal_type((2).denominator) # revealed: Literal[1] ``` @@ -1698,8 +1698,10 @@ Most attribute accesses on bool-literal types are delegated to `builtins.bool`, bools are instances of that class: ```py -reveal_type(True.__and__) # revealed: -reveal_type(False.__or__) # revealed: +# revealed: bound method Literal[True].__and__(**kwargs: @Todo(todo signature **kwargs)) -> @Todo(return type of overloaded function) +reveal_type(True.__and__) +# revealed: bound method Literal[False].__or__(**kwargs: @Todo(todo signature **kwargs)) -> @Todo(return type of overloaded function) +reveal_type(False.__or__) ``` Some attributes are special-cased, however: @@ -1714,8 +1716,9 @@ reveal_type(False.real) # revealed: Literal[0] All attribute access on literal `bytes` types is currently delegated to `builtins.bytes`: ```py -reveal_type(b"foo".join) # revealed: -reveal_type(b"foo".endswith) # revealed: +reveal_type(b"foo".join) # revealed: bound method Literal[b"foo"].join(iterable_of_bytes: @Todo(generics), /) -> bytes +# revealed: bound method Literal[b"foo"].endswith(suffix: @Todo(Support for `typing.TypeAlias`), start: SupportsIndex | None = ellipsis, end: SupportsIndex | None = ellipsis, /) -> bool +reveal_type(b"foo".endswith) ``` ## Instance attribute edge cases diff --git a/crates/red_knot_python_semantic/resources/mdtest/binary/custom.md b/crates/red_knot_python_semantic/resources/mdtest/binary/custom.md index 8fdca951c6..8b0b6741e5 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/binary/custom.md +++ b/crates/red_knot_python_semantic/resources/mdtest/binary/custom.md @@ -350,30 +350,30 @@ reveal_type(no() + no()) # revealed: Unknown def f(): pass -# error: [unsupported-operator] "Operator `+` is unsupported between objects of type `Literal[f]` and `Literal[f]`" +# error: [unsupported-operator] "Operator `+` is unsupported between objects of type `def f() -> Unknown` and `def f() -> Unknown`" reveal_type(f + f) # revealed: Unknown -# error: [unsupported-operator] "Operator `-` is unsupported between objects of type `Literal[f]` and `Literal[f]`" +# error: [unsupported-operator] "Operator `-` is unsupported between objects of type `def f() -> Unknown` and `def f() -> Unknown`" reveal_type(f - f) # revealed: Unknown -# error: [unsupported-operator] "Operator `*` is unsupported between objects of type `Literal[f]` and `Literal[f]`" +# error: [unsupported-operator] "Operator `*` is unsupported between objects of type `def f() -> Unknown` and `def f() -> Unknown`" reveal_type(f * f) # revealed: Unknown -# error: [unsupported-operator] "Operator `@` is unsupported between objects of type `Literal[f]` and `Literal[f]`" +# error: [unsupported-operator] "Operator `@` is unsupported between objects of type `def f() -> Unknown` and `def f() -> Unknown`" reveal_type(f @ f) # revealed: Unknown -# error: [unsupported-operator] "Operator `/` is unsupported between objects of type `Literal[f]` and `Literal[f]`" +# error: [unsupported-operator] "Operator `/` is unsupported between objects of type `def f() -> Unknown` and `def f() -> Unknown`" reveal_type(f / f) # revealed: Unknown -# error: [unsupported-operator] "Operator `%` is unsupported between objects of type `Literal[f]` and `Literal[f]`" +# error: [unsupported-operator] "Operator `%` is unsupported between objects of type `def f() -> Unknown` and `def f() -> Unknown`" reveal_type(f % f) # revealed: Unknown -# error: [unsupported-operator] "Operator `**` is unsupported between objects of type `Literal[f]` and `Literal[f]`" +# error: [unsupported-operator] "Operator `**` is unsupported between objects of type `def f() -> Unknown` and `def f() -> Unknown`" reveal_type(f**f) # revealed: Unknown -# error: [unsupported-operator] "Operator `<<` is unsupported between objects of type `Literal[f]` and `Literal[f]`" +# error: [unsupported-operator] "Operator `<<` is unsupported between objects of type `def f() -> Unknown` and `def f() -> Unknown`" reveal_type(f << f) # revealed: Unknown -# error: [unsupported-operator] "Operator `>>` is unsupported between objects of type `Literal[f]` and `Literal[f]`" +# error: [unsupported-operator] "Operator `>>` is unsupported between objects of type `def f() -> Unknown` and `def f() -> Unknown`" reveal_type(f >> f) # revealed: Unknown -# error: [unsupported-operator] "Operator `|` is unsupported between objects of type `Literal[f]` and `Literal[f]`" +# error: [unsupported-operator] "Operator `|` is unsupported between objects of type `def f() -> Unknown` and `def f() -> Unknown`" reveal_type(f | f) # revealed: Unknown -# error: [unsupported-operator] "Operator `^` is unsupported between objects of type `Literal[f]` and `Literal[f]`" +# error: [unsupported-operator] "Operator `^` is unsupported between objects of type `def f() -> Unknown` and `def f() -> Unknown`" reveal_type(f ^ f) # revealed: Unknown -# error: [unsupported-operator] "Operator `&` is unsupported between objects of type `Literal[f]` and `Literal[f]`" +# error: [unsupported-operator] "Operator `&` is unsupported between objects of type `def f() -> Unknown` and `def f() -> Unknown`" reveal_type(f & f) # revealed: Unknown -# error: [unsupported-operator] "Operator `//` is unsupported between objects of type `Literal[f]` and `Literal[f]`" +# error: [unsupported-operator] "Operator `//` is unsupported between objects of type `def f() -> Unknown` and `def f() -> Unknown`" reveal_type(f // f) # revealed: Unknown ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/call/getattr_static.md b/crates/red_knot_python_semantic/resources/mdtest/call/getattr_static.md index 2bf7a52d4e..79b0c821d6 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/call/getattr_static.md +++ b/crates/red_knot_python_semantic/resources/mdtest/call/getattr_static.md @@ -57,7 +57,8 @@ We can access attributes on objects of all kinds: import sys reveal_type(inspect.getattr_static(sys, "dont_write_bytecode")) # revealed: bool -reveal_type(inspect.getattr_static(inspect, "getattr_static")) # revealed: Literal[getattr_static] +# revealed: def getattr_static(obj: object, attr: str, default: Any | None = ellipsis) -> Any +reveal_type(inspect.getattr_static(inspect, "getattr_static")) reveal_type(inspect.getattr_static(1, "real")) # revealed: property ``` @@ -143,8 +144,9 @@ from typing import Any def _(a: Any, tuple_of_any: tuple[Any]): reveal_type(inspect.getattr_static(a, "x", "default")) # revealed: Any | Literal["default"] - # TODO: Ideally, this would just be `Literal[index]` - reveal_type(inspect.getattr_static(tuple_of_any, "index", "default")) # revealed: Literal[index] | Literal["default"] + # TODO: Ideally, this would just be `def index(self, value: Any, start: SupportsIndex = Literal[0], stop: SupportsIndex = int, /) -> int` + # revealed: (def index(self, value: Any, start: SupportsIndex = Literal[0], stop: SupportsIndex = int, /) -> int) | Literal["default"] + reveal_type(inspect.getattr_static(tuple_of_any, "index", "default")) ``` [official documentation]: https://docs.python.org/3/library/inspect.html#inspect.getattr_static diff --git a/crates/red_knot_python_semantic/resources/mdtest/call/methods.md b/crates/red_knot_python_semantic/resources/mdtest/call/methods.md index c09e749388..aad3c01e84 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/call/methods.md +++ b/crates/red_knot_python_semantic/resources/mdtest/call/methods.md @@ -32,20 +32,20 @@ the latter case, it returns a *bound method* object: ```py from inspect import getattr_static -reveal_type(getattr_static(C, "f")) # revealed: Literal[f] +reveal_type(getattr_static(C, "f")) # revealed: def f(self, x: int) -> str reveal_type(getattr_static(C, "f").__get__) # revealed: -reveal_type(getattr_static(C, "f").__get__(None, C)) # revealed: Literal[f] -reveal_type(getattr_static(C, "f").__get__(C(), C)) # revealed: +reveal_type(getattr_static(C, "f").__get__(None, C)) # revealed: def f(self, x: int) -> str +reveal_type(getattr_static(C, "f").__get__(C(), C)) # revealed: bound method C.f(x: int) -> str ``` In conclusion, this is why we see the following two types when accessing the `f` attribute on the class object `C` and on an instance `C()`: ```py -reveal_type(C.f) # revealed: Literal[f] -reveal_type(C().f) # revealed: +reveal_type(C.f) # revealed: def f(self, x: int) -> str +reveal_type(C().f) # revealed: bound method C.f(x: int) -> str ``` A bound method is a callable object that contains a reference to the `instance` that it was called @@ -56,7 +56,7 @@ via `__func__`): bound_method = C().f reveal_type(bound_method.__self__) # revealed: C -reveal_type(bound_method.__func__) # revealed: Literal[f] +reveal_type(bound_method.__func__) # revealed: def f(self, x: int) -> str ``` When we call the bound method, the `instance` is implicitly passed as the first argument (`self`): @@ -80,13 +80,13 @@ When we access methods from derived classes, they will be bound to instances of class D(C): pass -reveal_type(D().f) # revealed: +reveal_type(D().f) # revealed: bound method D.f(x: int) -> str ``` If we access an attribute on a bound method object itself, it will defer to `types.MethodType`: ```py -reveal_type(bound_method.__hash__) # revealed: +reveal_type(bound_method.__hash__) # revealed: bound method MethodType.__hash__() -> int ``` If an attribute is not available on the bound method object, it will be looked up on the underlying @@ -181,10 +181,10 @@ class B: return "a" def f(a_or_b: A | B, any_or_a: Any | A): - reveal_type(a_or_b.f) # revealed: | + reveal_type(a_or_b.f) # revealed: (bound method A.f() -> int) | (bound method B.f() -> str) reveal_type(a_or_b.f()) # revealed: int | str - reveal_type(any_or_a.f) # revealed: Any | + reveal_type(any_or_a.f) # revealed: Any | (bound method A.f() -> int) reveal_type(any_or_a.f()) # revealed: Any | int ``` @@ -198,7 +198,7 @@ python-version = "3.12" ```py type IntOrStr = int | str -reveal_type(IntOrStr.__or__) # revealed: +reveal_type(IntOrStr.__or__) # revealed: bound method typing.TypeAliasType.__or__(right: Any) -> _SpecialForm ``` ## Error cases: Calling `__get__` for methods @@ -270,7 +270,7 @@ class Meta(type): class C(metaclass=Meta): pass -reveal_type(C.f) # revealed: +reveal_type(C.f) # revealed: bound method Literal[C].f(arg: int) -> str reveal_type(C.f(1)) # revealed: str ``` @@ -322,8 +322,8 @@ class C: def f(cls: type[C], x: int) -> str: return "a" -reveal_type(C.f) # revealed: -reveal_type(C().f) # revealed: +reveal_type(C.f) # revealed: bound method Literal[C].f(x: int) -> str +reveal_type(C().f) # revealed: bound method type[C].f(x: int) -> str ``` The `cls` method argument is then implicitly passed as the first argument when calling the method: @@ -360,8 +360,8 @@ When a class method is accessed on a derived class, it is bound to that derived class Derived(C): pass -reveal_type(Derived.f) # revealed: -reveal_type(Derived().f) # revealed: +reveal_type(Derived.f) # revealed: bound method Literal[Derived].f(x: int) -> str +reveal_type(Derived().f) # revealed: bound method type[Derived].f(x: int) -> str reveal_type(Derived.f(1)) # revealed: str reveal_type(Derived().f(1)) # revealed: str @@ -379,22 +379,22 @@ class C: @classmethod def f(cls): ... -reveal_type(getattr_static(C, "f")) # revealed: Literal[f] +reveal_type(getattr_static(C, "f")) # revealed: def f(cls) -> Unknown reveal_type(getattr_static(C, "f").__get__) # revealed: ``` But we correctly model how the `classmethod` descriptor works: ```py -reveal_type(getattr_static(C, "f").__get__(None, C)) # revealed: -reveal_type(getattr_static(C, "f").__get__(C(), C)) # revealed: -reveal_type(getattr_static(C, "f").__get__(C())) # revealed: +reveal_type(getattr_static(C, "f").__get__(None, C)) # revealed: bound method Literal[C].f() -> Unknown +reveal_type(getattr_static(C, "f").__get__(C(), C)) # revealed: bound method Literal[C].f() -> Unknown +reveal_type(getattr_static(C, "f").__get__(C())) # revealed: bound method type[C].f() -> Unknown ``` The `owner` argument takes precedence over the `instance` argument: ```py -reveal_type(getattr_static(C, "f").__get__("dummy", C)) # revealed: +reveal_type(getattr_static(C, "f").__get__("dummy", C)) # revealed: bound method Literal[C].f() -> Unknown ``` ### Classmethods mixed with other decorators diff --git a/crates/red_knot_python_semantic/resources/mdtest/decorators.md b/crates/red_knot_python_semantic/resources/mdtest/decorators.md index 0d957b2dc7..3ba75ec10b 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/decorators.md +++ b/crates/red_knot_python_semantic/resources/mdtest/decorators.md @@ -207,7 +207,7 @@ first argument: def wrong_signature(f: int) -> str: return "a" -# error: [invalid-argument-type] "Argument to this function is incorrect: Expected `int`, found `Literal[f]`" +# error: [invalid-argument-type] "Argument to this function is incorrect: Expected `int`, found `def f(x) -> Unknown`" @wrong_signature def f(x): ... diff --git a/crates/red_knot_python_semantic/resources/mdtest/descriptor_protocol.md b/crates/red_knot_python_semantic/resources/mdtest/descriptor_protocol.md index f23409a62f..aeabb336ec 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/descriptor_protocol.md +++ b/crates/red_knot_python_semantic/resources/mdtest/descriptor_protocol.md @@ -563,18 +563,18 @@ from inspect import getattr_static def f(x: object) -> str: return "a" -reveal_type(f) # revealed: Literal[f] +reveal_type(f) # revealed: def f(x: object) -> str reveal_type(f.__get__) # revealed: -reveal_type(f.__get__(None, type(f))) # revealed: Literal[f] +reveal_type(f.__get__(None, type(f))) # revealed: def f(x: object) -> str reveal_type(f.__get__(None, type(f))(1)) # revealed: str wrapper_descriptor = getattr_static(f, "__get__") reveal_type(wrapper_descriptor) # revealed: -reveal_type(wrapper_descriptor(f, None, type(f))) # revealed: Literal[f] +reveal_type(wrapper_descriptor(f, None, type(f))) # revealed: def f(x: object) -> str # Attribute access on the method-wrapper `f.__get__` falls back to `MethodWrapperType`: -reveal_type(f.__get__.__hash__) # revealed: +reveal_type(f.__get__.__hash__) # revealed: bound method MethodWrapperType.__hash__() -> int # Attribute access on the wrapper-descriptor falls back to `WrapperDescriptorType`: reveal_type(wrapper_descriptor.__qualname__) # revealed: str @@ -587,7 +587,7 @@ class C: ... bound_method = wrapper_descriptor(f, C(), C) -reveal_type(bound_method) # revealed: +reveal_type(bound_method) # revealed: bound method C.f() -> str ``` We can then call it, and the instance of `C` is implicitly passed to the first parameter of `f` diff --git a/crates/red_knot_python_semantic/resources/mdtest/exception/control_flow.md b/crates/red_knot_python_semantic/resources/mdtest/exception/control_flow.md index 3357427dae..2db3cf362d 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/exception/control_flow.md +++ b/crates/red_knot_python_semantic/resources/mdtest/exception/control_flow.md @@ -591,9 +591,9 @@ try: reveal_type(x) # revealed: B | D reveal_type(x) # revealed: B | D x = foo - reveal_type(x) # revealed: Literal[foo] + reveal_type(x) # revealed: def foo(param=A) -> Unknown except: - reveal_type(x) # revealed: Literal[1] | Literal[foo] + reveal_type(x) # revealed: Literal[1] | (def foo(param=A) -> Unknown) class Bar: x = could_raise_returns_E() @@ -603,9 +603,9 @@ except: reveal_type(x) # revealed: Literal[Bar] finally: # TODO: should be `Literal[1] | Literal[foo] | Literal[Bar]` - reveal_type(x) # revealed: Literal[foo] | Literal[Bar] + reveal_type(x) # revealed: (def foo(param=A) -> Unknown) | Literal[Bar] -reveal_type(x) # revealed: Literal[foo] | Literal[Bar] +reveal_type(x) # revealed: (def foo(param=A) -> Unknown) | Literal[Bar] ``` [1]: https://astral-sh.notion.site/Exception-handler-control-flow-11348797e1ca80bb8ce1e9aedbbe439d diff --git a/crates/red_knot_python_semantic/resources/mdtest/generics/scoping.md b/crates/red_knot_python_semantic/resources/mdtest/generics/scoping.md index f38070751a..31561d442d 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/generics/scoping.md +++ b/crates/red_knot_python_semantic/resources/mdtest/generics/scoping.md @@ -102,18 +102,18 @@ class C[T]: def f(self, x: T) -> str: return "a" -reveal_type(getattr_static(C[int], "f")) # revealed: Literal[f[int]] +reveal_type(getattr_static(C[int], "f")) # revealed: def f(self, x: int) -> str reveal_type(getattr_static(C[int], "f").__get__) # revealed: -reveal_type(getattr_static(C[int], "f").__get__(None, C[int])) # revealed: Literal[f[int]] -# revealed: +reveal_type(getattr_static(C[int], "f").__get__(None, C[int])) # revealed: def f(self, x: int) -> str +# revealed: bound method C[int].f(x: int) -> str reveal_type(getattr_static(C[int], "f").__get__(C[int](), C[int])) -reveal_type(C[int].f) # revealed: Literal[f[int]] -reveal_type(C[int]().f) # revealed: +reveal_type(C[int].f) # revealed: def f(self, x: int) -> str +reveal_type(C[int]().f) # revealed: bound method C[int].f(x: int) -> str bound_method = C[int]().f reveal_type(bound_method.__self__) # revealed: C[int] -reveal_type(bound_method.__func__) # revealed: Literal[f[int]] +reveal_type(bound_method.__func__) # revealed: def f(self, x: int) -> str reveal_type(C[int]().f(1)) # revealed: str reveal_type(bound_method(1)) # revealed: str @@ -124,7 +124,7 @@ reveal_type(C[int].f(C[int](), 1)) # revealed: str class D[U](C[U]): pass -reveal_type(D[int]().f) # revealed: +reveal_type(D[int]().f) # revealed: bound method D[int].f(x: int) -> str ``` ## Methods can mention other typevars diff --git a/crates/red_knot_python_semantic/resources/mdtest/import/builtins.md b/crates/red_knot_python_semantic/resources/mdtest/import/builtins.md index 5a1af24fe7..1b2305fb41 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/import/builtins.md +++ b/crates/red_knot_python_semantic/resources/mdtest/import/builtins.md @@ -7,7 +7,7 @@ Builtin symbols can be explicitly imported: ```py import builtins -reveal_type(builtins.chr) # revealed: Literal[chr] +reveal_type(builtins.chr) # revealed: def chr(i: int | SupportsIndex, /) -> str ``` ## Implicit use of builtin @@ -15,7 +15,7 @@ reveal_type(builtins.chr) # revealed: Literal[chr] Or used implicitly: ```py -reveal_type(chr) # revealed: Literal[chr] +reveal_type(chr) # revealed: def chr(i: int | SupportsIndex, /) -> str reveal_type(str) # revealed: Literal[str] ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/import/conditional.md b/crates/red_knot_python_semantic/resources/mdtest/import/conditional.md index 26e849666f..703bf6078f 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/import/conditional.md +++ b/crates/red_knot_python_semantic/resources/mdtest/import/conditional.md @@ -103,8 +103,8 @@ else: ```py from b import f -# TODO: We should disambiguate in such cases, showing `Literal[b.f, c.f]`. -reveal_type(f) # revealed: Literal[f, f] +# TODO: We should disambiguate in such cases between `b.f` and `c.f`. +reveal_type(f) # revealed: (def f() -> Unknown) | (def f() -> Unknown) ``` ## Reimport with stub declaration diff --git a/crates/red_knot_python_semantic/resources/mdtest/import/star.md b/crates/red_knot_python_semantic/resources/mdtest/import/star.md index 99aa39e74e..de8c84d465 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/import/star.md +++ b/crates/red_knot_python_semantic/resources/mdtest/import/star.md @@ -1034,8 +1034,8 @@ from exporter import * # At runtime, `f` is imported but `g` is not; to avoid false positives, however, # we treat `a` as though it does not have `__all__` at all, # which would imply that both symbols would be present. -reveal_type(f) # revealed: Literal[f] -reveal_type(g) # revealed: Literal[g] +reveal_type(f) # revealed: def f() -> str +reveal_type(g) # revealed: def g() -> int ``` ### `__all__` conditionally defined in a statically known branch @@ -1198,7 +1198,7 @@ f() ```py from a import * -reveal_type(f) # revealed: Literal[f] +reveal_type(f) # revealed: def f() -> Unknown # TODO: we're undecided about whether we should consider this a false positive or not. # Mutating the global scope to add a symbol from an inner scope will not *necessarily* result diff --git a/crates/red_knot_python_semantic/resources/mdtest/loops/for.md b/crates/red_knot_python_semantic/resources/mdtest/loops/for.md index bc18230147..8fec8a52ee 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/loops/for.md +++ b/crates/red_knot_python_semantic/resources/mdtest/loops/for.md @@ -321,7 +321,7 @@ def _(flag: bool): # TODO... `int` might be ideal here? reveal_type(x) # revealed: int | Unknown - # error: [not-iterable] "Object of type `Iterable2` may not be iterable because its `__iter__` attribute (with type ` | None`) may not be callable" + # error: [not-iterable] "Object of type `Iterable2` may not be iterable because its `__iter__` attribute (with type `(bound method Iterable2.__iter__() -> Iterator) | None`) may not be callable" for y in Iterable2(): # TODO... `int` might be ideal here? reveal_type(y) # revealed: int | Unknown diff --git a/crates/red_knot_python_semantic/resources/mdtest/narrow/truthiness.md b/crates/red_knot_python_semantic/resources/mdtest/narrow/truthiness.md index 057c4d706b..84a14f93f5 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/narrow/truthiness.md +++ b/crates/red_knot_python_semantic/resources/mdtest/narrow/truthiness.md @@ -63,7 +63,7 @@ def bar(world: str, *args, **kwargs) -> float: x = foo if flag() else bar if x: - reveal_type(x) # revealed: Literal[foo, bar] + reveal_type(x) # revealed: (def foo(hello: int) -> bytes) | (def bar(world: str, *args, **kwargs) -> int | float) else: reveal_type(x) # revealed: Never ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/properties.md b/crates/red_knot_python_semantic/resources/mdtest/properties.md index df35515d5a..2a24f2cd5f 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/properties.md +++ b/crates/red_knot_python_semantic/resources/mdtest/properties.md @@ -146,7 +146,7 @@ class C: @property def attr(self) -> int: return 1 - # error: [invalid-argument-type] "Argument to this function is incorrect: Expected `(Any, Any, /) -> None`, found `Literal[attr]`" + # error: [invalid-argument-type] "Argument to this function is incorrect: Expected `(Any, Any, /) -> None`, found `def attr(self) -> None`" @attr.setter def attr(self) -> None: pass @@ -156,7 +156,7 @@ class C: ```py class C: - # error: [invalid-argument-type] "Argument to this function is incorrect: Expected `((Any, /) -> Any) | None`, found `Literal[attr]`" + # error: [invalid-argument-type] "Argument to this function is incorrect: Expected `((Any, /) -> Any) | None`, found `def attr(self, x: int) -> int`" @property def attr(self, x: int) -> int: return 1 @@ -294,10 +294,10 @@ Properties also have `fget` and `fset` attributes that can be used to retrieve t and setter functions, respectively. ```py -reveal_type(attr_property.fget) # revealed: Literal[attr] +reveal_type(attr_property.fget) # revealed: def attr(self) -> int reveal_type(attr_property.fget(c)) # revealed: int -reveal_type(attr_property.fset) # revealed: Literal[attr] +reveal_type(attr_property.fset) # revealed: def attr(self, value: str) -> None reveal_type(attr_property.fset(c, "a")) # revealed: None # error: [invalid-argument-type] diff --git a/crates/red_knot_python_semantic/resources/mdtest/scopes/builtin.md b/crates/red_knot_python_semantic/resources/mdtest/scopes/builtin.md index c51b5a0fb5..5aa4175f8e 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/scopes/builtin.md +++ b/crates/red_knot_python_semantic/resources/mdtest/scopes/builtin.md @@ -13,7 +13,7 @@ if returns_bool(): chr: int = 1 def f(): - reveal_type(chr) # revealed: int | Literal[chr] + reveal_type(chr) # revealed: int | (def chr(i: int | SupportsIndex, /) -> str) ``` ## Conditionally global or builtin, with annotation @@ -28,5 +28,5 @@ if returns_bool(): chr: int = 1 def f(): - reveal_type(chr) # revealed: int | Literal[chr] + reveal_type(chr) # revealed: int | (def chr(i: int | SupportsIndex, /) -> str) ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/scopes/moduletype_attrs.md b/crates/red_knot_python_semantic/resources/mdtest/scopes/moduletype_attrs.md index aa6d47cd87..65d9a930af 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/scopes/moduletype_attrs.md +++ b/crates/red_knot_python_semantic/resources/mdtest/scopes/moduletype_attrs.md @@ -51,10 +51,10 @@ inside the module: import typing reveal_type(typing.__name__) # revealed: str -reveal_type(typing.__init__) # revealed: +reveal_type(typing.__init__) # revealed: bound method ModuleType.__init__(name: str, doc: str | None = ellipsis) -> None # These come from `builtins.object`, not `types.ModuleType`: -reveal_type(typing.__eq__) # revealed: +reveal_type(typing.__eq__) # revealed: bound method ModuleType.__eq__(value: object, /) -> bool reveal_type(typing.__class__) # revealed: Literal[ModuleType] diff --git a/crates/red_knot_python_semantic/resources/mdtest/shadowing/function.md b/crates/red_knot_python_semantic/resources/mdtest/shadowing/function.md index 1a2b7cbcb5..ea976c3593 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/shadowing/function.md +++ b/crates/red_knot_python_semantic/resources/mdtest/shadowing/function.md @@ -37,17 +37,17 @@ reveal_type(f) # revealed: Literal[1] def f(): ... -reveal_type(f) # revealed: Literal[f] +reveal_type(f) # revealed: def f() -> Unknown def f(x: int) -> int: raise NotImplementedError -reveal_type(f) # revealed: Literal[f] +reveal_type(f) # revealed: def f(x: int) -> int f: int = 1 reveal_type(f) # revealed: Literal[1] def f(): ... -reveal_type(f) # revealed: Literal[f] +reveal_type(f) # revealed: def f() -> Unknown ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly-not-callable_`__getitem__`_method.snap b/crates/red_knot_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly-not-callable_`__getitem__`_method.snap index 6a0de7346b..62695a6d1c 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly-not-callable_`__getitem__`_method.snap +++ b/crates/red_knot_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly-not-callable_`__getitem__`_method.snap @@ -78,7 +78,7 @@ error: lint:not-iterable | 26 | # error: [not-iterable] 27 | for y in Iterable2(): - | ^^^^^^^^^^^ Object of type `Iterable2` may not be iterable because it has no `__iter__` method and its `__getitem__` attribute (with type ` | None`) may not be callable + | ^^^^^^^^^^^ Object of type `Iterable2` may not be iterable because it has no `__iter__` method and its `__getitem__` attribute (with type `(bound method Iterable2.__getitem__(key: int) -> int) | None`) may not be callable 28 | # TODO... `int` might be ideal here? 29 | reveal_type(y) # revealed: int | Unknown | diff --git a/crates/red_knot_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__getitem__`_methods.snap b/crates/red_knot_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__getitem__`_methods.snap index 9ac73cf75b..1a0330059d 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__getitem__`_methods.snap +++ b/crates/red_knot_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__getitem__`_methods.snap @@ -48,7 +48,7 @@ error: lint:not-iterable | 19 | # error: [not-iterable] 20 | for x in Iterable1(): - | ^^^^^^^^^^^ Object of type `Iterable1` may not be iterable because it has no `__iter__` method and its `__getitem__` attribute (with type ` | None`) may not be callable + | ^^^^^^^^^^^ Object of type `Iterable1` may not be iterable because it has no `__iter__` method and its `__getitem__` attribute (with type `(bound method Iterable1.__getitem__(item: int) -> str) | None`) may not be callable 21 | # TODO: `str` might be better 22 | reveal_type(x) # revealed: str | Unknown | @@ -75,7 +75,7 @@ error: lint:not-iterable | 24 | # error: [not-iterable] 25 | for y in Iterable2(): - | ^^^^^^^^^^^ Object of type `Iterable2` may not be iterable because it has no `__iter__` method and its `__getitem__` method (with type ` | `) may have an incorrect signature for the old-style iteration protocol (expected a signature at least as permissive as `def __getitem__(self, key: int): ...`) + | ^^^^^^^^^^^ Object of type `Iterable2` may not be iterable because it has no `__iter__` method and its `__getitem__` method (with type `(bound method Iterable2.__getitem__(item: int) -> str) | (bound method Iterable2.__getitem__(item: str) -> int)`) may have an incorrect signature for the old-style iteration protocol (expected a signature at least as permissive as `def __getitem__(self, key: int): ...`) 26 | reveal_type(y) # revealed: str | int | diff --git a/crates/red_knot_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__iter__`_methods.snap b/crates/red_knot_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__iter__`_methods.snap index 5f5ed188f0..d59db051e7 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__iter__`_methods.snap +++ b/crates/red_knot_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__iter__`_methods.snap @@ -52,7 +52,7 @@ error: lint:not-iterable | 16 | # error: [not-iterable] 17 | for x in Iterable1(): - | ^^^^^^^^^^^ Object of type `Iterable1` may not be iterable because its `__iter__` method (with type ` | `) may have an invalid signature (expected `def __iter__(self): ...`) + | ^^^^^^^^^^^ Object of type `Iterable1` may not be iterable because its `__iter__` method (with type `(bound method Iterable1.__iter__() -> Iterator) | (bound method Iterable1.__iter__(invalid_extra_arg) -> Iterator)`) may have an invalid signature (expected `def __iter__(self): ...`) 18 | reveal_type(x) # revealed: int | @@ -78,7 +78,7 @@ error: lint:not-iterable | 27 | # error: [not-iterable] 28 | for x in Iterable2(): - | ^^^^^^^^^^^ Object of type `Iterable2` may not be iterable because its `__iter__` attribute (with type ` | None`) may not be callable + | ^^^^^^^^^^^ Object of type `Iterable2` may not be iterable because its `__iter__` attribute (with type `(bound method Iterable2.__iter__() -> Iterator) | None`) may not be callable 29 | # TODO: `int` would probably be better here: 30 | reveal_type(x) # revealed: int | Unknown | diff --git a/crates/red_knot_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_invalid_`__getitem__`.snap b/crates/red_knot_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_invalid_`__getitem__`.snap index c7578e8c71..90a743e300 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_invalid_`__getitem__`.snap +++ b/crates/red_knot_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_invalid_`__getitem__`.snap @@ -59,7 +59,7 @@ error: lint:not-iterable | 30 | # error: [not-iterable] 31 | for x in Iterable1(): - | ^^^^^^^^^^^ Object of type `Iterable1` may not be iterable because it may not have an `__iter__` method and its `__getitem__` attribute (with type ` | None`) may not be callable + | ^^^^^^^^^^^ Object of type `Iterable1` may not be iterable because it may not have an `__iter__` method and its `__getitem__` attribute (with type `(bound method Iterable1.__getitem__(item: int) -> str) | None`) may not be callable 32 | # TODO: `bytes | str` might be better 33 | reveal_type(x) # revealed: bytes | str | Unknown | @@ -86,7 +86,7 @@ error: lint:not-iterable | 35 | # error: [not-iterable] 36 | for y in Iterable2(): - | ^^^^^^^^^^^ Object of type `Iterable2` may not be iterable because it may not have an `__iter__` method and its `__getitem__` method (with type ` | `) may have an incorrect signature for the old-style iteration protocol (expected a signature at least as permissive as `def __getitem__(self, key: int): ...`) + | ^^^^^^^^^^^ Object of type `Iterable2` may not be iterable because it may not have an `__iter__` method and its `__getitem__` method (with type `(bound method Iterable2.__getitem__(item: int) -> str) | (bound method Iterable2.__getitem__(item: str) -> int)`) may have an incorrect signature for the old-style iteration protocol (expected a signature at least as permissive as `def __getitem__(self, key: int): ...`) 37 | reveal_type(y) # revealed: bytes | str | int | diff --git a/crates/red_knot_python_semantic/resources/mdtest/type_of/dynamic.md b/crates/red_knot_python_semantic/resources/mdtest/type_of/dynamic.md index 13fb8760fe..4e59d97eae 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/type_of/dynamic.md +++ b/crates/red_knot_python_semantic/resources/mdtest/type_of/dynamic.md @@ -35,7 +35,7 @@ in strict mode. ```py def f(x: type): reveal_type(x) # revealed: type - reveal_type(x.__repr__) # revealed: + reveal_type(x.__repr__) # revealed: bound method type.__repr__() -> str class A: ... @@ -50,7 +50,7 @@ x: type = A() # error: [invalid-assignment] ```py def f(x: type[object]): reveal_type(x) # revealed: type - reveal_type(x.__repr__) # revealed: + reveal_type(x.__repr__) # revealed: bound method type.__repr__() -> str class A: ... diff --git a/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_assignable_to.md b/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_assignable_to.md index 33d730c189..b128fe8013 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_assignable_to.md +++ b/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_assignable_to.md @@ -500,7 +500,7 @@ def g(x: Any) -> int: c: Callable[[Any], str] = f -# error: [invalid-assignment] "Object of type `Literal[g]` is not assignable to `(Any, /) -> str`" +# error: [invalid-assignment] "Object of type `def g(x: Any) -> int` is not assignable to `(Any, /) -> str`" c: Callable[[Any], str] = g ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/unary/custom.md b/crates/red_knot_python_semantic/resources/mdtest/unary/custom.md index ea46a0ae6f..1ad79dd3bc 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/unary/custom.md +++ b/crates/red_knot_python_semantic/resources/mdtest/unary/custom.md @@ -80,11 +80,11 @@ reveal_type(~No) # revealed: Unknown def f(): pass -# error: [unsupported-operator] "Unary operator `+` is unsupported for type `Literal[f]`" +# error: [unsupported-operator] "Unary operator `+` is unsupported for type `def f() -> Unknown`" reveal_type(+f) # revealed: Unknown -# error: [unsupported-operator] "Unary operator `-` is unsupported for type `Literal[f]`" +# error: [unsupported-operator] "Unary operator `-` is unsupported for type `def f() -> Unknown`" reveal_type(-f) # revealed: Unknown -# error: [unsupported-operator] "Unary operator `~` is unsupported for type `Literal[f]`" +# error: [unsupported-operator] "Unary operator `~` is unsupported for type `def f() -> Unknown`" reveal_type(~f) # revealed: Unknown ``` diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index a8ae1a112f..7795658be7 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -244,11 +244,31 @@ impl std::fmt::Display for TodoType { /// It can be created by specifying a custom message: `todo_type!("PEP 604 not supported")`. #[cfg(debug_assertions)] macro_rules! todo_type { - ($message:literal) => { + ($message:literal) => {{ + const _: () = { + let s = $message; + + if !s.is_ascii() { + panic!("todo_type! message must be ASCII"); + } + + let bytes = s.as_bytes(); + let mut i = 0; + while i < bytes.len() { + // Check each byte for '(' or ')' + let ch = bytes[i]; + + assert!( + !40u8.eq_ignore_ascii_case(&ch) && !41u8.eq_ignore_ascii_case(&ch), + "todo_type! message must not contain parentheses", + ); + i += 1; + } + }; $crate::types::Type::Dynamic($crate::types::DynamicType::Todo($crate::types::TodoType( $message, ))) - }; + }}; ($message:ident) => { $crate::types::Type::Dynamic($crate::types::DynamicType::Todo($crate::types::TodoType( $message, @@ -2521,6 +2541,10 @@ impl<'db> Type<'db> { Type::MethodWrapper(MethodWrapperKind::FunctionTypeDunderGet(function)), ) .into(), + Type::FunctionLiteral(function) if name == "__call__" => Symbol::bound( + Type::MethodWrapper(MethodWrapperKind::FunctionTypeDunderCall(function)), + ) + .into(), Type::PropertyInstance(property) if name == "__get__" => Symbol::bound( Type::MethodWrapper(MethodWrapperKind::PropertyDunderGet(property)), ) @@ -3879,7 +3903,7 @@ impl<'db> Type<'db> { } Some(builder.build()) } - Type::Intersection(_) => Some(todo_type!("Type::Intersection.to_instance()")), + Type::Intersection(_) => Some(todo_type!("Type::Intersection.to_instance")), Type::BooleanLiteral(_) | Type::BytesLiteral(_) | Type::FunctionLiteral(_) @@ -4241,6 +4265,12 @@ impl<'db> Type<'db> { )) } + Type::MethodWrapper(MethodWrapperKind::FunctionTypeDunderCall(function)) => { + Type::MethodWrapper(MethodWrapperKind::FunctionTypeDunderCall( + function.apply_specialization(db, specialization), + )) + } + Type::MethodWrapper(MethodWrapperKind::PropertyDunderGet(property)) => { Type::MethodWrapper(MethodWrapperKind::PropertyDunderGet( property.apply_specialization(db, specialization), @@ -5694,6 +5724,8 @@ impl<'db> CallableType<'db> { pub enum MethodWrapperKind<'db> { /// Method wrapper for `some_function.__get__` FunctionTypeDunderGet(FunctionType<'db>), + /// Method wrapper for `some_function.__call__` + FunctionTypeDunderCall(FunctionType<'db>), /// Method wrapper for `some_property.__get__` PropertyDunderGet(PropertyInstanceType<'db>), /// Method wrapper for `some_property.__set__` diff --git a/crates/red_knot_python_semantic/src/types/call/bind.rs b/crates/red_knot_python_semantic/src/types/call/bind.rs index 8cbb2c2aa8..e270329636 100644 --- a/crates/red_knot_python_semantic/src/types/call/bind.rs +++ b/crates/red_knot_python_semantic/src/types/call/bind.rs @@ -528,7 +528,7 @@ impl<'db> Bindings<'db> { } Some(KnownFunction::Overload) => { - overload.set_return_type(todo_type!("overload(..) return type")); + overload.set_return_type(todo_type!("overload[..] return type")); } Some(KnownFunction::GetattrStatic) => { diff --git a/crates/red_knot_python_semantic/src/types/display.rs b/crates/red_knot_python_semantic/src/types/display.rs index 84747c0157..599bceabd8 100644 --- a/crates/red_knot_python_semantic/src/types/display.rs +++ b/crates/red_knot_python_semantic/src/types/display.rs @@ -44,8 +44,7 @@ impl Display for DisplayType<'_> { | Type::StringLiteral(_) | Type::BytesLiteral(_) | Type::ClassLiteral(_) - | Type::GenericAlias(_) - | Type::FunctionLiteral(_) => { + | Type::GenericAlias(_) => { write!(f, "Literal[{representation}]") } _ => representation.fmt(f), @@ -95,35 +94,32 @@ impl Display for DisplayRepresentation<'_> { }, Type::KnownInstance(known_instance) => f.write_str(known_instance.repr(self.db)), Type::FunctionLiteral(function) => { - f.write_str(function.name(self.db))?; - if let Some(specialization) = function.specialization(self.db) { - specialization.display_short(self.db).fmt(f)?; - } - Ok(()) + let signature = function.signature(self.db); + // TODO: when generic function types are supported, we should add + // the generic type parameters to the signature, i.e. + // show `def foo[T](x: T) -> T`. + + write!( + f, + // "def {name}{specialization}{signature}", + "def {name}{signature}", + name = function.name(self.db), + signature = signature.display(self.db) + ) } Type::Callable(callable) => callable.signature(self.db).display(self.db).fmt(f), Type::BoundMethod(bound_method) => { let function = bound_method.function(self.db); - let self_instance = bound_method.self_instance(self.db); - let self_instance_specialization = match self_instance { - Type::Instance(InstanceType { - class: ClassType::Generic(alias), - }) => Some(alias.specialization(self.db)), - _ => None, - }; - let specialization = match function.specialization(self.db) { - Some(specialization) - if self_instance_specialization.is_none_or(|sis| specialization == sis) => - { - specialization.display_short(self.db).to_string() - } - _ => String::new(), - }; + + // TODO: use the specialization from the method. Similar to the comment above + // about the function specialization, + write!( f, - "", + "bound method {instance}.{method}{signature}", method = function.name(self.db), instance = bound_method.self_instance(self.db).display(self.db), + signature = function.signature(self.db).bind_self().display(self.db) ) } Type::MethodWrapper(MethodWrapperKind::FunctionTypeDunderGet(function)) => { @@ -139,6 +135,19 @@ impl Display for DisplayRepresentation<'_> { }, ) } + Type::MethodWrapper(MethodWrapperKind::FunctionTypeDunderCall(function)) => { + write!( + f, + "", + function = function.name(self.db), + specialization = if let Some(specialization) = function.specialization(self.db) + { + specialization.display_short(self.db).to_string() + } else { + String::new() + }, + ) + } Type::MethodWrapper(MethodWrapperKind::PropertyDunderGet(_)) => { write!(f, "",) } @@ -463,7 +472,6 @@ impl Display for DisplayLiteralGroup<'_> { #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] enum CondensedDisplayTypeKind { Class, - Function, LiteralExpression, } @@ -473,7 +481,6 @@ impl TryFrom> for CondensedDisplayTypeKind { fn try_from(value: Type<'_>) -> Result { match value { Type::ClassLiteral(_) => Ok(Self::Class), - Type::FunctionLiteral(_) => Ok(Self::Function), Type::IntLiteral(_) | Type::StringLiteral(_) | Type::BytesLiteral(_) @@ -551,7 +558,11 @@ struct DisplayMaybeParenthesizedType<'db> { impl Display for DisplayMaybeParenthesizedType<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - if let Type::Callable(_) | Type::MethodWrapper(_) = self.ty { + if let Type::Callable(_) + | Type::MethodWrapper(_) + | Type::FunctionLiteral(_) + | Type::BoundMethod(_) = self.ty + { write!(f, "({})", self.ty.display(self.db)) } else { self.ty.display(self.db).fmt(f)