Skip to content

Commit c74ba00

Browse files
authored
[red-knot] Fix more redundant-cast false positives (#17119)
## Summary There are quite a few places we infer `Todo` types currently, and some of them are nested somewhat deeply in type expressions. These can cause spurious issues for the new `redundant-cast` diagnostics. We fixed all the false positives we saw in the mypy_primer report before merging #17100, but I think there are still lots of places where we'd emit false positives due to this check -- we currently don't run on that many projects at all in our mypy_primer check: https://github.com/astral-sh/ruff/blob/d0c8eaa0923352ccc4ec30c9fac1f573f20998b3/.github/workflows/mypy_primer.yaml#L71 This PR fixes some more false positives from this diagnostic by making the `Type::contains_todo()` method more expansive. ## Test Plan I added a regression test which causes us to emit a spurious diagnostic on `main`, but does not with this PR.
1 parent a15404a commit c74ba00

File tree

2 files changed

+65
-2
lines changed
  • crates/red_knot_python_semantic

2 files changed

+65
-2
lines changed

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,14 @@ def function_returning_any() -> Any:
3838
# error: [redundant-cast] "Value is already of type `Any`"
3939
cast(Any, function_returning_any())
4040
```
41+
42+
Complex type expressions (which may be unsupported) do not lead to spurious `[redundant-cast]`
43+
diagnostics.
44+
45+
```py
46+
from typing import Callable
47+
48+
def f(x: Callable[[dict[str, int]], None], y: tuple[dict[str, int]]):
49+
a = cast(Callable[[list[bytes]], None], x)
50+
b = cast(tuple[list[bytes]], y)
51+
```

crates/red_knot_python_semantic/src/types.rs

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -321,8 +321,60 @@ impl<'db> Type<'db> {
321321
}
322322

323323
pub fn contains_todo(&self, db: &'db dyn Db) -> bool {
324-
self.is_todo()
325-
|| matches!(self, Type::Union(union) if union.elements(db).iter().any(Type::is_todo))
324+
match self {
325+
Self::Dynamic(DynamicType::Todo(_) | DynamicType::TodoProtocol) => true,
326+
327+
Self::AlwaysFalsy
328+
| Self::AlwaysTruthy
329+
| Self::Never
330+
| Self::BooleanLiteral(_)
331+
| Self::BytesLiteral(_)
332+
| Self::FunctionLiteral(_)
333+
| Self::Instance(_)
334+
| Self::ModuleLiteral(_)
335+
| Self::ClassLiteral(_)
336+
| Self::KnownInstance(_)
337+
| Self::StringLiteral(_)
338+
| Self::IntLiteral(_)
339+
| Self::LiteralString
340+
| Self::SliceLiteral(_)
341+
| Self::Dynamic(DynamicType::Unknown | DynamicType::Any)
342+
| Self::Callable(
343+
CallableType::BoundMethod(_)
344+
| CallableType::WrapperDescriptorDunderGet
345+
| CallableType::MethodWrapperDunderGet(_),
346+
) => false,
347+
348+
Self::Callable(CallableType::General(callable)) => {
349+
let signature = callable.signature(db);
350+
signature.parameters().iter().any(|param| {
351+
param
352+
.annotated_type()
353+
.is_some_and(|ty| ty.contains_todo(db))
354+
}) || signature.return_ty.is_some_and(|ty| ty.contains_todo(db))
355+
}
356+
357+
Self::SubclassOf(subclass_of) => match subclass_of.subclass_of() {
358+
ClassBase::Dynamic(DynamicType::Todo(_) | DynamicType::TodoProtocol) => true,
359+
ClassBase::Dynamic(DynamicType::Unknown | DynamicType::Any) => false,
360+
ClassBase::Class(_) => false,
361+
},
362+
363+
Self::Tuple(tuple) => tuple.elements(db).iter().any(|ty| ty.contains_todo(db)),
364+
365+
Self::Union(union) => union.elements(db).iter().any(|ty| ty.contains_todo(db)),
366+
367+
Self::Intersection(intersection) => {
368+
intersection
369+
.positive(db)
370+
.iter()
371+
.any(|ty| ty.contains_todo(db))
372+
|| intersection
373+
.negative(db)
374+
.iter()
375+
.any(|ty| ty.contains_todo(db))
376+
}
377+
}
326378
}
327379

328380
pub const fn class_literal(class: Class<'db>) -> Self {

0 commit comments

Comments
 (0)