From 6de2b2873b33fb0844c53a663e05e9290454bc49 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Wed, 12 Mar 2025 12:13:22 +0530 Subject: [PATCH] [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. --- .../mdtest/type_properties/is_fully_static.md | 27 +++++++++++++++-- crates/red_knot_python_semantic/src/types.rs | 30 +++++++++++++++---- 2 files changed, 50 insertions(+), 7 deletions(-) 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.