[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
https://github.com/astral-sh/ruff/pull/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:


d0c8eaa092/.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.
This commit is contained in:
Alex Waygood 2025-04-01 19:03:42 +01:00 committed by GitHub
parent a15404a5c1
commit c74ba00219
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 65 additions and 2 deletions

View file

@ -38,3 +38,14 @@ def function_returning_any() -> Any:
# error: [redundant-cast] "Value is already of type `Any`"
cast(Any, function_returning_any())
```
Complex type expressions (which may be unsupported) do not lead to spurious `[redundant-cast]`
diagnostics.
```py
from typing import Callable
def f(x: Callable[[dict[str, int]], None], y: tuple[dict[str, int]]):
a = cast(Callable[[list[bytes]], None], x)
b = cast(tuple[list[bytes]], y)
```

View file

@ -321,8 +321,60 @@ impl<'db> Type<'db> {
}
pub fn contains_todo(&self, db: &'db dyn Db) -> bool {
self.is_todo()
|| matches!(self, Type::Union(union) if union.elements(db).iter().any(Type::is_todo))
match self {
Self::Dynamic(DynamicType::Todo(_) | DynamicType::TodoProtocol) => true,
Self::AlwaysFalsy
| Self::AlwaysTruthy
| Self::Never
| Self::BooleanLiteral(_)
| Self::BytesLiteral(_)
| Self::FunctionLiteral(_)
| Self::Instance(_)
| Self::ModuleLiteral(_)
| Self::ClassLiteral(_)
| Self::KnownInstance(_)
| Self::StringLiteral(_)
| Self::IntLiteral(_)
| Self::LiteralString
| Self::SliceLiteral(_)
| Self::Dynamic(DynamicType::Unknown | DynamicType::Any)
| Self::Callable(
CallableType::BoundMethod(_)
| CallableType::WrapperDescriptorDunderGet
| CallableType::MethodWrapperDunderGet(_),
) => false,
Self::Callable(CallableType::General(callable)) => {
let signature = callable.signature(db);
signature.parameters().iter().any(|param| {
param
.annotated_type()
.is_some_and(|ty| ty.contains_todo(db))
}) || signature.return_ty.is_some_and(|ty| ty.contains_todo(db))
}
Self::SubclassOf(subclass_of) => match subclass_of.subclass_of() {
ClassBase::Dynamic(DynamicType::Todo(_) | DynamicType::TodoProtocol) => true,
ClassBase::Dynamic(DynamicType::Unknown | DynamicType::Any) => false,
ClassBase::Class(_) => false,
},
Self::Tuple(tuple) => tuple.elements(db).iter().any(|ty| ty.contains_todo(db)),
Self::Union(union) => union.elements(db).iter().any(|ty| ty.contains_todo(db)),
Self::Intersection(intersection) => {
intersection
.positive(db)
.iter()
.any(|ty| ty.contains_todo(db))
|| intersection
.negative(db)
.iter()
.any(|ty| ty.contains_todo(db))
}
}
}
pub const fn class_literal(class: Class<'db>) -> Self {