[red-knot] Check if callable type is fully static (#16633)

## Summary

Part of #15382 

This PR adds the check for whether a callable type is fully static or
not.

A callable type is fully static if all of the parameter types are fully
static _and_ the return type is fully static _and_ if it does not use
the gradual form (`...`) for its parameters.

## Test Plan

Update `is_fully_static.md` with callable types.

It seems that currently this test is grouped into either fully static or
not, I think it would be useful to split them up in groups like
callable, etc. I intentionally avoided that in this PR but I'll put up a
PR for an appropriate split.

Note: I've an explicit goal of updating the property tests with the new
callable types once all relations are implemented.
This commit is contained in:
Dhruv Manilawala 2025-03-12 12:13:22 +05:30 committed by GitHub
parent 6b84253679
commit 6de2b2873b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 50 additions and 7 deletions

View file

@ -5,7 +5,7 @@ A type is fully static iff it does not contain any gradual forms.
## Fully-static
```py
from typing_extensions import Literal, LiteralString, Never
from typing_extensions import Literal, LiteralString, Never, Callable
from knot_extensions import Intersection, Not, TypeOf, is_fully_static, static_assert
static_assert(is_fully_static(Never))
@ -38,7 +38,7 @@ static_assert(is_fully_static(type[object]))
## Non-fully-static
```py
from typing_extensions import Any, Literal, LiteralString
from typing_extensions import Any, Literal, LiteralString, Callable
from knot_extensions import Intersection, Not, TypeOf, Unknown, is_fully_static, static_assert
static_assert(not is_fully_static(Any))
@ -52,3 +52,26 @@ static_assert(not is_fully_static(tuple[Any, ...]))
static_assert(not is_fully_static(tuple[int, Any]))
static_assert(not is_fully_static(type[Any]))
```
## Callable
```py
from typing_extensions import Callable, Any
from knot_extensions import Unknown, is_fully_static, static_assert
static_assert(is_fully_static(Callable[[], int]))
static_assert(is_fully_static(Callable[[int, str], int]))
static_assert(not is_fully_static(Callable[..., int]))
static_assert(not is_fully_static(Callable[[], Any]))
static_assert(not is_fully_static(Callable[[int, Unknown], int]))
```
The invalid forms of `Callable` annotation are never fully static because we represent them with the
`(...) -> Unknown` signature.
```py
static_assert(not is_fully_static(Callable))
# error: [invalid-type-form]
static_assert(not is_fully_static(Callable[int, int]))
```

View file

@ -1331,11 +1331,7 @@ impl<'db> Type<'db> {
.elements(db)
.iter()
.all(|elem| elem.is_fully_static(db)),
Type::Callable(CallableType::General(_)) => {
// TODO: `Callable` is not fully static when the parameter argument is `...` or
// when any parameter type or return type is not fully static.
false
}
Type::Callable(CallableType::General(callable)) => callable.is_fully_static(db),
}
}
@ -4518,6 +4514,30 @@ impl<'db> GeneralCallableType<'db> {
Signature::new(Parameters::unknown(), Some(Type::unknown())),
)
}
/// Returns `true` if this is a fully static callable type.
///
/// A callable type is fully static if all of its parameters and return type are fully static
/// and if it does not use gradual form (`...`) for its parameters.
pub(crate) fn is_fully_static(self, db: &'db dyn Db) -> bool {
let signature = self.signature(db);
if signature.parameters().is_gradual() {
return false;
}
if signature.parameters().iter().any(|parameter| {
parameter
.annotated_type()
.is_some_and(|annotated_type| !annotated_type.is_fully_static(db))
}) {
return false;
}
signature
.return_ty
.is_some_and(|return_type| return_type.is_fully_static(db))
}
}
/// A type that represents callable objects.