Skip to content

Commit ca0cce3

Browse files
authored
[red-knot] Fix more [redundant-cast] false positives (#17170)
Fixes #17164. Simply checking whether one type is gradually equivalent to another is too simplistic here: `Any` is gradually equivalent to `Todo`, but we should permit users to cast from `Todo` or `Unknown` to `Any` without complaining about it. This changes our logic so that we only complain about redundant casts if: - the two types are exactly equal (when normalized) OR they are equivalent (we'll still complain about `Any -> Any` casts, and about `Any | str | int` -> `str | int | Any` casts, since their normalized forms are exactly equal, even though the type is not fully static -- and therefore does not participate in equivalence relations) - AND the casted type does not contain `Todo`
1 parent 3f00010 commit ca0cce3

File tree

2 files changed

+27
-4
lines changed
  • crates/red_knot_python_semantic

2 files changed

+27
-4
lines changed

crates/red_knot_python_semantic/resources/mdtest/directives/cast.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,22 @@ def f(x: Callable[[dict[str, int]], None], y: tuple[dict[str, int]]):
4949
a = cast(Callable[[list[bytes]], None], x)
5050
b = cast(tuple[list[bytes]], y)
5151
```
52+
53+
A cast from `Todo` or `Unknown` to `Any` is not considered a "redundant cast": even if these are
54+
understood as gradually equivalent types by red-knot, they are understood as different types by
55+
human readers of red-knot's output. For `Unknown` in particular, we may consider it differently in
56+
the context of some opt-in diagnostics, as it indicates that the gradual type has come about due to
57+
an invalid annotation, missing annotation or missing type argument somewhere.
58+
59+
```py
60+
from knot_extensions import Unknown
61+
62+
def f(x: Any, y: Unknown, z: Any | str | int):
63+
a = cast(dict[str, Any], x)
64+
reveal_type(a) # revealed: @Todo(generics)
65+
66+
b = cast(Any, y)
67+
reveal_type(b) # revealed: Any
68+
69+
c = cast(str | int | Any, z) # error: [redundant-cast]
70+
```

crates/red_knot_python_semantic/src/types/infer.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4049,16 +4049,20 @@ impl<'db> TypeInferenceBuilder<'db> {
40494049
}
40504050
}
40514051
KnownFunction::Cast => {
4052-
if let [Some(casted_ty), Some(source_ty)] = overload.parameter_types() {
4053-
if source_ty.is_gradual_equivalent_to(self.context.db(), *casted_ty)
4054-
&& !source_ty.contains_todo(self.context.db())
4052+
if let [Some(casted_type), Some(source_type)] =
4053+
overload.parameter_types()
4054+
{
4055+
let db = self.db();
4056+
if (source_type.is_equivalent_to(db, *casted_type)
4057+
|| source_type.normalized(db) == casted_type.normalized(db))
4058+
&& !source_type.contains_todo(db)
40554059
{
40564060
self.context.report_lint(
40574061
&REDUNDANT_CAST,
40584062
call_expression,
40594063
format_args!(
40604064
"Value is already of type `{}`",
4061-
casted_ty.display(self.context.db()),
4065+
casted_type.display(db),
40624066
),
40634067
);
40644068
}

0 commit comments

Comments
 (0)