[red-knot] ClassLiteral(<T>) is not a disjoint type from Instance(<metaclass of T>) (#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.
This commit is contained in:
Alex Waygood 2024-12-14 19:28:09 +00:00 committed by GitHub
parent ac31b26a0e
commit 4d64cdb83c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 24 additions and 4 deletions

View file

@ -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<Ty>),
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);

View file

@ -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()