Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions crates/red_knot_ide/src/hover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -231,7 +231,7 @@ mod tests {
| |
| source
|
");
"###);
}

#[test]
Expand Down Expand Up @@ -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
Expand All @@ -329,7 +329,7 @@ mod tests {
| |
| source
|
");
"###);
}

#[test]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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__`
Expand Down
17 changes: 10 additions & 7 deletions crates/red_knot_python_semantic/resources/mdtest/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -1285,7 +1285,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:
Expand Down Expand Up @@ -1584,7 +1584,7 @@ Some attributes are special-cased, however:

```py
reveal_type(f.__get__) # revealed: <method-wrapper `__get__` of `f`>
reveal_type(f.__call__) # revealed: <bound method `__call__` of `Literal[f]`>
reveal_type(f.__call__) # revealed: <method-wrapper `__call__` of `f`>
```

### Int-literal attributes
Expand All @@ -1593,7 +1593,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: <bound method `bit_length` of `Literal[2]`>
reveal_type((2).bit_length) # revealed: bound method Literal[2].bit_length() -> int
reveal_type((2).denominator) # revealed: Literal[1]
```

Expand All @@ -1610,8 +1610,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: <bound method `__and__` of `Literal[True]`>
reveal_type(False.__or__) # revealed: <bound method `__or__` of `Literal[False]`>
# 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:
Expand All @@ -1626,8 +1628,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: <bound method `join` of `Literal[b"foo"]`>
reveal_type(b"foo".endswith) # revealed: <bound method `endswith` of `Literal[b"foo"]`>
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
Expand Down
26 changes: 13 additions & 13 deletions crates/red_knot_python_semantic/resources/mdtest/binary/custom.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
Expand Down Expand Up @@ -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
42 changes: 21 additions & 21 deletions crates/red_knot_python_semantic/resources/mdtest/call/methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: <method-wrapper `__get__` of `f`>

reveal_type(getattr_static(C, "f").__get__(None, C)) # revealed: Literal[f]
reveal_type(getattr_static(C, "f").__get__(C(), C)) # revealed: <bound method `f` of `C`>
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: <bound method `f` of `C`>
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
Expand All @@ -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`):
Expand All @@ -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: <bound method `f` of `D`>
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: <bound method `__hash__` of `MethodType`>
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
Expand Down Expand Up @@ -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: <bound method `f` of `A`> | <bound method `f` of `B`>
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 | <bound method `f` of `A`>
reveal_type(any_or_a.f) # revealed: Any | (bound method A.f() -> int)
reveal_type(any_or_a.f()) # revealed: Any | int
```

Expand All @@ -198,7 +198,7 @@ python-version = "3.12"
```py
type IntOrStr = int | str

reveal_type(IntOrStr.__or__) # revealed: <bound method `__or__` of `typing.TypeAliasType`>
reveal_type(IntOrStr.__or__) # revealed: bound method typing.TypeAliasType.__or__(right: Any) -> _SpecialForm
```

## Error cases: Calling `__get__` for methods
Expand Down Expand Up @@ -270,7 +270,7 @@ class Meta(type):
class C(metaclass=Meta):
pass

reveal_type(C.f) # revealed: <bound method `f` of `Literal[C]`>
reveal_type(C.f) # revealed: bound method Literal[C].f(arg: int) -> str
reveal_type(C.f(1)) # revealed: str
```

Expand Down Expand Up @@ -322,8 +322,8 @@ class C:
def f(cls: type[C], x: int) -> str:
return "a"

reveal_type(C.f) # revealed: <bound method `f` of `Literal[C]`>
reveal_type(C().f) # revealed: <bound method `f` of `type[C]`>
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:
Expand Down Expand Up @@ -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: <bound method `f` of `Literal[Derived]`>
reveal_type(Derived().f) # revealed: <bound method `f` of `type[Derived]`>
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
Expand All @@ -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: <method-wrapper `__get__` of `f`>
```

But we correctly model how the `classmethod` descriptor works:

```py
reveal_type(getattr_static(C, "f").__get__(None, C)) # revealed: <bound method `f` of `Literal[C]`>
reveal_type(getattr_static(C, "f").__get__(C(), C)) # revealed: <bound method `f` of `Literal[C]`>
reveal_type(getattr_static(C, "f").__get__(C())) # revealed: <bound method `f` of `type[C]`>
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: <bound method `f` of `Literal[C]`>
reveal_type(getattr_static(C, "f").__get__("dummy", C)) # revealed: bound method Literal[C].f() -> Unknown
```

### Classmethods mixed with other decorators
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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): ...

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: <method-wrapper `__get__` of `f`>
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: <wrapper-descriptor `__get__` of `function` objects>
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: <bound method `__hash__` of `MethodWrapperType`>
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
Expand All @@ -587,7 +587,7 @@ class C: ...

bound_method = wrapper_descriptor(f, C(), C)

reveal_type(bound_method) # revealed: <bound method `f` of `C`>
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`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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
Loading
Loading