[red-knot] Add new property tests for subtyping with "bottom" callable (#17635)

## 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
```
This commit is contained in:
Dhruv Manilawala 2025-04-26 03:58:13 +05:30 committed by GitHub
parent 6ab32a7746
commit 0251679f87
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 39 additions and 1 deletions

View File

@ -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.

View File

@ -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,

View File

@ -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>,