[red-knot] detect invalid return type (#16540)

## Summary

This PR closes #16248.

If the return type of the function isn't assignable to the one
specified, an `invalid-return-type` error occurs.
I thought it would be better to report this as a different kind of error
than the `invalid-assignment` error, so I defined this as a new error.

## Test Plan

All type inconsistencies in the test cases have been replaced with
appropriate ones.

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
This commit is contained in:
Shunsuke Shibayama
2025-03-12 10:58:59 +09:00
committed by GitHub
parent e17cd350b6
commit 78b5f0b165
43 changed files with 983 additions and 103 deletions

View File

@@ -35,7 +35,7 @@ Each typevar must also appear _somewhere_ in the parameter list:
```py
def absurd[T]() -> T:
# There's no way to construct a T!
...
raise ValueError("absurd")
```
## Inferring generic function parameter types
@@ -48,7 +48,8 @@ whether we want to infer a more specific `Literal` type where possible, or use h
the inferred type to e.g. `int`.
```py
def f[T](x: T) -> T: ...
def f[T](x: T) -> T:
return x
# TODO: no error
# TODO: revealed: int or Literal[1]
@@ -77,7 +78,8 @@ The matching up of call arguments and discovery of constraints on typevars can b
process for arbitrarily-nested generic types in parameters.
```py
def f[T](x: list[T]) -> T: ...
def f[T](x: list[T]) -> T:
return x[0]
# TODO: revealed: float
reveal_type(f([1.0, 2.0])) # revealed: T
@@ -119,7 +121,7 @@ def different_types[T, S](cond: bool, t: T, s: S) -> T:
if cond:
return t
else:
# TODO: error: S is not assignable to T
# error: [invalid-return-type] "Object of type `S` is not assignable to return type `T`"
return s
def same_types[T](cond: bool, t1: T, t2: T) -> T:

View File

@@ -37,8 +37,11 @@ from typing import TypeVar
T = TypeVar("T")
def f1(x: T) -> T: ...
def f2(x: T) -> T: ...
def f1(x: T) -> T:
return x
def f2(x: T) -> T:
return x
f1(1)
f2("a")
@@ -53,7 +56,8 @@ This also applies to a single generic function being used multiple times, instan
to a different type each time.
```py
def f[T](x: T) -> T: ...
def f[T](x: T) -> T:
return x
# TODO: no error
# TODO: revealed: int or Literal[1]
@@ -72,8 +76,11 @@ reveal_type(f("a")) # revealed: T
```py
class C[T]:
def m1(self, x: T) -> T: ...
def m2(self, x: T) -> T: ...
def m1(self, x: T) -> T:
return x
def m2(self, x: T) -> T:
return x
c: C[int] = C()
# TODO: no error
@@ -101,7 +108,8 @@ S = TypeVar("S")
# TODO: no error
# error: [invalid-base]
class Legacy(Generic[T]):
def m(self, x: T, y: S) -> S: ...
def m(self, x: T, y: S) -> S:
return y
legacy: Legacy[int] = Legacy()
# TODO: revealed: str
@@ -112,7 +120,8 @@ With PEP 695 syntax, it is clearer that the method uses a separate typevar:
```py
class C[T]:
def m[S](self, x: T, y: S) -> S: ...
def m[S](self, x: T, y: S) -> S:
return y
c: C[int] = C()
# TODO: no errors
@@ -147,7 +156,8 @@ class C(Generic[T]):
x: list[S] = []
# This is not an error, as shown in the previous test
def m(self, x: S) -> S: ...
def m(self, x: S) -> S:
return x
```
This is true with PEP 695 syntax, as well, though we must use the legacy syntax to define the
@@ -169,8 +179,11 @@ class C[T]:
# TODO: error
x: list[S] = []
def m1(self, x: S) -> S: ...
def m2[S](self, x: S) -> S: ...
def m1(self, x: S) -> S:
return x
def m2[S](self, x: S) -> S:
return x
```
## Nested formal typevars must be distinct