From 0251679f871a0888aa62169771d3a718874fd479 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Sat, 26 Apr 2025 03:58:13 +0530 Subject: [PATCH] [red-knot] Add new property tests for subtyping with "bottom" callable (#17635) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary I remember we discussed about adding this as a property tests so here I am. ## Test Plan ```console ❯ QUICKCHECK_TESTS=10000000 cargo test --locked --release --package red_knot_python_semantic -- --ignored types::property_tests::stable::bottom_callable_is_subtype_of_all_fully_static_callable Finished `release` profile [optimized] target(s) in 0.10s Running unittests src/lib.rs (target/release/deps/red_knot_python_semantic-e41596ca2dbd0e98) running 1 test test types::property_tests::stable::bottom_callable_is_subtype_of_all_fully_static_callable ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 233 filtered out; finished in 30.91s ``` --- crates/red_knot_python_semantic/src/types.rs | 17 +++++++++++++++++ .../src/types/property_tests.rs | 10 +++++++++- .../src/types/signatures.rs | 13 +++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 5b4c37d39c..070adc5aab 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -537,6 +537,11 @@ impl<'db> Type<'db> { matches!(self, Type::Never) } + /// Returns `true` if `self` is [`Type::Callable`]. + pub const fn is_callable_type(&self) -> bool { + matches!(self, Type::Callable(..)) + } + fn is_none(&self, db: &'db dyn Db) -> bool { self.into_instance() .is_some_and(|instance| instance.class().is_known(db, KnownClass::NoneType)) @@ -6571,6 +6576,18 @@ impl<'db> CallableType<'db> { ) } + /// Create a callable type which represents a fully-static "bottom" callable. + /// + /// Specifically, this represents a callable type with a single signature: + /// `(*args: object, **kwargs: object) -> Never`. + #[cfg(test)] + pub(crate) fn bottom(db: &'db dyn Db) -> Type<'db> { + Type::Callable(CallableType::single( + db, + Signature::new(Parameters::object(db), Some(Type::Never)), + )) + } + /// Return a "normalized" version of this `Callable` type. /// /// See [`Type::normalized`] for more details. diff --git a/crates/red_knot_python_semantic/src/types/property_tests.rs b/crates/red_knot_python_semantic/src/types/property_tests.rs index cbcfef74ac..77c83e04e0 100644 --- a/crates/red_knot_python_semantic/src/types/property_tests.rs +++ b/crates/red_knot_python_semantic/src/types/property_tests.rs @@ -58,7 +58,7 @@ macro_rules! type_property_test { mod stable { use super::union; - use crate::types::Type; + use crate::types::{CallableType, Type}; // Reflexivity: `T` is equivalent to itself. type_property_test!( @@ -169,6 +169,14 @@ mod stable { forall types t. t.is_fully_static(db) => Type::Never.is_subtype_of(db, t) ); + // Similar to `Never`, a fully-static "bottom" callable type should be a subtype of all + // fully-static callable types + type_property_test!( + bottom_callable_is_subtype_of_all_fully_static_callable, db, + forall types t. t.is_callable_type() && t.is_fully_static(db) + => CallableType::bottom(db).is_subtype_of(db, t) + ); + // For any two fully static types, each type in the pair must be a subtype of their union. type_property_test!( all_fully_static_type_pairs_are_subtype_of_their_union, db, diff --git a/crates/red_knot_python_semantic/src/types/signatures.rs b/crates/red_knot_python_semantic/src/types/signatures.rs index 5e931c5d96..4fcd669be2 100644 --- a/crates/red_knot_python_semantic/src/types/signatures.rs +++ b/crates/red_knot_python_semantic/src/types/signatures.rs @@ -934,6 +934,19 @@ impl<'db> Parameters<'db> { } } + /// Return parameters that represents `(*args: object, **kwargs: object)`. + #[cfg(test)] + pub(crate) fn object(db: &'db dyn Db) -> Self { + Self { + value: vec![ + Parameter::variadic(Name::new_static("args")).with_annotated_type(Type::object(db)), + Parameter::keyword_variadic(Name::new_static("kwargs")) + .with_annotated_type(Type::object(db)), + ], + is_gradual: false, + } + } + fn from_parameters( db: &'db dyn Db, definition: Definition<'db>,