Skip to content

Commit

Permalink
[red-knot] Never is callable and iterable. Arbitrary attributes can b…
Browse files Browse the repository at this point in the history
…e accessed. (#16533)

## Summary

- `Never` is callable
- `Never` is iterable
- Arbitrary attributes can be accessed on `Never`

Split out from #16416 that is going to be required.

## Test Plan

Tests for all properties above.
  • Loading branch information
sharkdp authored Mar 6, 2025
1 parent a25be46 commit 0a627ef
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 5 deletions.
14 changes: 14 additions & 0 deletions crates/red_knot_python_semantic/resources/mdtest/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -1209,6 +1209,20 @@ class C:
reveal_type(C().x) # revealed: Unknown
```

### Accessing attributes on `Never`

Arbitrary attributes can be accessed on `Never` without emitting any errors:

```py
from typing_extensions import Never

def f(never: Never):
reveal_type(never.arbitrary_attribute) # revealed: Never

# Assigning `Never` to an attribute on `Never` is also allowed:
never.another_attribute = never
```

### Builtin types attributes

This test can probably be removed eventually, but we currently include it because we do not yet
Expand Down
12 changes: 12 additions & 0 deletions crates/red_knot_python_semantic/resources/mdtest/call/never.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Never is callable

The type `Never` is callable with an arbitrary set of arguments. The result is always `Never`.

```py
from typing_extensions import Never

def f(never: Never):
reveal_type(never()) # revealed: Never
reveal_type(never(1)) # revealed: Never
reveal_type(never(1, "a", never, x=None)) # revealed: Never
```
10 changes: 10 additions & 0 deletions crates/red_knot_python_semantic/resources/mdtest/loops/for.md
Original file line number Diff line number Diff line change
Expand Up @@ -737,3 +737,13 @@ def _(flag: bool, flag2: bool):
for y in Iterable2():
reveal_type(y) # revealed: bytes | str | int
```

## Never is iterable

```py
from typing_extensions import Never

def f(never: Never):
for x in never:
reveal_type(x) # revealed: Never
```
11 changes: 6 additions & 5 deletions crates/red_knot_python_semantic/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1366,9 +1366,7 @@ impl<'db> Type<'db> {
#[must_use]
fn static_member(&self, db: &'db dyn Db, name: &str) -> Symbol<'db> {
match self {
Type::Dynamic(_) => Symbol::bound(self),

Type::Never => Symbol::todo("attribute lookup on Never"),
Type::Dynamic(_) | Type::Never => Symbol::bound(self),

Type::FunctionLiteral(_) => KnownClass::FunctionType
.to_instance(db)
Expand Down Expand Up @@ -2267,8 +2265,11 @@ impl<'db> Type<'db> {
})
}

// Dynamic types are callable, and the return type is the same dynamic type
Type::Dynamic(_) => Ok(CallOutcome::Single(CallBinding::from_return_type(self))),
// Dynamic types are callable, and the return type is the same dynamic type. Similarly,
// `Never` is always callable and returns `Never`.
Type::Dynamic(_) | Type::Never => {
Ok(CallOutcome::Single(CallBinding::from_return_type(self)))
}

Type::Union(union) => {
CallOutcome::try_call_union(db, union, |element| element.try_call(db, arguments))
Expand Down

0 comments on commit 0a627ef

Please sign in to comment.