From 4d64cdb83c6c5944d8f455ae7cf0d129d9ff5ede Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Sat, 14 Dec 2024 19:28:09 +0000 Subject: [PATCH] [red-knot] `ClassLiteral()` is not a disjoint type from `Instance()` (#14970) ## Summary A class is an instance of its metaclass, so `ClassLiteral("ABC")` is not disjoint from `Instance("ABCMeta")`. However, we erroneously consider the two types disjoint on the `main` branch. This PR fixes that. This bug was uncovered by adding some more core types to the property tests that provide coverage for classes that have custom metaclasses. The additions to the property tests are included in this PR. ## Test Plan New unit tests and property tests added. Tested with: - `cargo test -p red_knot_python_semantic` - `QUICKCHECK_TESTS=100000 cargo test -p red_knot_python_semantic -- --ignored types::property_tests::stable` The assignability property test fails on this branch, but that's a known issue that exists on `main`, due to https://github.com/astral-sh/ruff/issues/14899. --- crates/red_knot_python_semantic/src/types.rs | 21 +++++++++++++++---- .../src/types/property_tests.rs | 7 +++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index a838a4abad859..b520549c72461 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -1157,10 +1157,15 @@ impl<'db> Type<'db> { Some(KnownClass::Slice | KnownClass::Object) ), - (Type::ClassLiteral(..), Type::Instance(InstanceType { class })) - | (Type::Instance(InstanceType { class }), Type::ClassLiteral(..)) => { - !matches!(class.known(db), Some(KnownClass::Type | KnownClass::Object)) - } + ( + Type::ClassLiteral(ClassLiteralType { class: class_a }), + Type::Instance(InstanceType { class: class_b }), + ) + | ( + Type::Instance(InstanceType { class: class_b }), + Type::ClassLiteral(ClassLiteralType { class: class_a }), + ) => !class_a.is_instance_of(db, class_b), + (Type::FunctionLiteral(..), Type::Instance(InstanceType { class })) | (Type::Instance(InstanceType { class }), Type::FunctionLiteral(..)) => !matches!( class.known(db), @@ -3352,6 +3357,7 @@ pub(crate) mod tests { Tuple(Vec), SubclassOfAny, SubclassOfBuiltinClass(&'static str), + SubclassOfAbcClass(&'static str), StdlibModule(CoreStdlibModule), SliceLiteral(i32, i32, i32), } @@ -3404,6 +3410,12 @@ pub(crate) mod tests { .expect_class_literal() .class, ), + Ty::SubclassOfAbcClass(s) => Type::subclass_of( + core_module_symbol(db, CoreStdlibModule::Abc, s) + .expect_type() + .expect_class_literal() + .class, + ), Ty::StdlibModule(module) => { Type::ModuleLiteral(resolve_module(db, &module.name()).unwrap().file()) } @@ -3744,6 +3756,7 @@ pub(crate) mod tests { #[test_case(Ty::Tuple(vec![Ty::IntLiteral(1), Ty::IntLiteral(2)]), Ty::Tuple(vec![Ty::IntLiteral(1), Ty::BuiltinInstance("int")]))] #[test_case(Ty::BuiltinClassLiteral("str"), Ty::BuiltinInstance("type"))] #[test_case(Ty::BuiltinClassLiteral("str"), Ty::SubclassOfAny)] + #[test_case(Ty::AbcClassLiteral("ABC"), Ty::AbcInstance("ABCMeta"))] fn is_not_disjoint_from(a: Ty, b: Ty) { let db = setup_db(); let a = a.into_type(&db); 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 381bffa7afde4..83f49ea22039a 100644 --- a/crates/red_knot_python_semantic/src/types/property_tests.rs +++ b/crates/red_knot_python_semantic/src/types/property_tests.rs @@ -65,9 +65,16 @@ fn arbitrary_core_type(g: &mut Gen) -> Ty { Ty::BuiltinClassLiteral("bool"), Ty::BuiltinClassLiteral("object"), Ty::BuiltinInstance("type"), + Ty::AbcInstance("ABC"), + Ty::AbcInstance("ABCMeta"), Ty::SubclassOfAny, Ty::SubclassOfBuiltinClass("object"), Ty::SubclassOfBuiltinClass("str"), + Ty::SubclassOfBuiltinClass("type"), + Ty::AbcClassLiteral("ABC"), + Ty::AbcClassLiteral("ABCMeta"), + Ty::SubclassOfAbcClass("ABC"), + Ty::SubclassOfAbcClass("ABCMeta"), ]) .unwrap() .clone()