diff --git a/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_fully_static.md b/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_fully_static.md index d7e9828e7a..eb5792c498 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_fully_static.md +++ b/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_fully_static.md @@ -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])) +``` diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 195a397d20..91b16019a4 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -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.