From 58930905ebff65ba6366fc60ef931323f13df8ff Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Thu, 12 Dec 2024 16:53:03 +0000 Subject: [PATCH] [red-knot] Fixup a few edge cases regarding `type[]` (#14918) --- crates/red_knot_python_semantic/src/stdlib.rs | 3 + crates/red_knot_python_semantic/src/types.rs | 62 ++++++++++++++----- 2 files changed, 48 insertions(+), 17 deletions(-) diff --git a/crates/red_knot_python_semantic/src/stdlib.rs b/crates/red_knot_python_semantic/src/stdlib.rs index 50efef19fe..56f3c984bc 100644 --- a/crates/red_knot_python_semantic/src/stdlib.rs +++ b/crates/red_knot_python_semantic/src/stdlib.rs @@ -15,6 +15,8 @@ pub(crate) enum CoreStdlibModule { TypingExtensions, Typing, Sys, + #[allow(dead_code)] + Abc, // currently only used in tests } impl CoreStdlibModule { @@ -26,6 +28,7 @@ impl CoreStdlibModule { Self::Typeshed => "_typeshed", Self::TypingExtensions => "typing_extensions", Self::Sys => "sys", + Self::Abc => "abc", } } diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 0109bb1a30..3047c8adcf 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -654,14 +654,19 @@ impl<'db> Type<'db> { }, ) } - (Type::ClassLiteral(self_class), Type::SubclassOf(target_class)) => { - self_class.class.is_subclass_of_base(db, target_class.base) - } ( - Type::Instance(InstanceType { class: self_class }), - Type::SubclassOf(target_class), - ) if self_class.is_known(db, KnownClass::Type) => { - self_class.is_subclass_of_base(db, target_class.base) + Type::ClassLiteral(ClassLiteralType { class: self_class }), + Type::SubclassOf(SubclassOfType { + base: ClassBase::Class(target_class), + }), + ) => self_class.is_subclass_of(db, target_class), + ( + Type::Instance(_), + Type::SubclassOf(SubclassOfType { + base: ClassBase::Class(target_class), + }), + ) if target_class.is_known(db, KnownClass::Object) => { + self.is_subtype_of(db, KnownClass::Type.to_instance(db)) } ( Type::SubclassOf(SubclassOfType { @@ -928,10 +933,18 @@ impl<'db> Type<'db> { | Type::ClassLiteral(..)), ) => left != right, - (Type::SubclassOf(type_class), Type::ClassLiteral(class_literal)) - | (Type::ClassLiteral(class_literal), Type::SubclassOf(type_class)) => { - !class_literal.class.is_subclass_of_base(db, type_class.base) - } + ( + Type::SubclassOf(SubclassOfType { + base: ClassBase::Class(class_a), + }), + Type::ClassLiteral(ClassLiteralType { class: class_b }), + ) + | ( + Type::ClassLiteral(ClassLiteralType { class: class_b }), + Type::SubclassOf(SubclassOfType { + base: ClassBase::Class(class_a), + }), + ) => !class_b.is_subclass_of(db, class_a), (Type::SubclassOf(_), Type::SubclassOf(_)) => false, (Type::SubclassOf(_), Type::Instance(_)) | (Type::Instance(_), Type::SubclassOf(_)) => { false @@ -2599,11 +2612,7 @@ impl<'db> Class<'db> { pub fn is_subclass_of(self, db: &'db dyn Db, other: Class) -> bool { // `is_subclass_of` is checking the subtype relation, in which gradual types do not // participate, so we should not return `True` if we find `Any/Unknown` in the MRO. - self.is_subclass_of_base(db, other) - } - - fn is_subclass_of_base(self, db: &'db dyn Db, other: impl Into>) -> bool { - self.iter_mro(db).contains(&other.into()) + self.iter_mro(db).contains(&ClassBase::Class(other)) } /// Return the explicit `metaclass` of this class, if one is defined. @@ -3038,12 +3047,18 @@ pub(crate) mod tests { // BuiltinInstance("str") corresponds to an instance of the builtin `str` class BuiltinInstance(&'static str), TypingInstance(&'static str), + /// Members of the `abc` stdlib module + AbcInstance(&'static str), + AbcClassLiteral(&'static str), TypingLiteral, // BuiltinClassLiteral("str") corresponds to the builtin `str` class object itself BuiltinClassLiteral(&'static str), KnownClassInstance(KnownClass), Union(Vec), - Intersection { pos: Vec, neg: Vec }, + Intersection { + pos: Vec, + neg: Vec, + }, Tuple(Vec), SubclassOfAny, SubclassOfBuiltinClass(&'static str), @@ -3063,6 +3078,12 @@ pub(crate) mod tests { Ty::LiteralString => Type::LiteralString, Ty::BytesLiteral(s) => Type::bytes_literal(db, s.as_bytes()), Ty::BuiltinInstance(s) => builtins_symbol(db, s).expect_type().to_instance(db), + Ty::AbcInstance(s) => core_module_symbol(db, CoreStdlibModule::Abc, s) + .expect_type() + .to_instance(db), + Ty::AbcClassLiteral(s) => { + core_module_symbol(db, CoreStdlibModule::Abc, s).expect_type() + } Ty::TypingInstance(s) => typing_symbol(db, s).expect_type().to_instance(db), Ty::TypingLiteral => Type::KnownInstance(KnownInstanceType::Literal), Ty::BuiltinClassLiteral(s) => builtins_symbol(db, s).expect_type(), @@ -3148,6 +3169,7 @@ pub(crate) mod tests { #[test_case(Ty::BuiltinInstance("type"), Ty::SubclassOfAny)] #[test_case(Ty::BuiltinInstance("type"), Ty::SubclassOfBuiltinClass("object"))] #[test_case(Ty::BuiltinInstance("type"), Ty::BuiltinInstance("type"))] + #[test_case(Ty::BuiltinClassLiteral("str"), Ty::SubclassOfAny)] fn is_assignable_to(from: Ty, to: Ty) { let db = setup_db(); assert!(from.into_type(&db).is_assignable_to(&db, to.into_type(&db))); @@ -3214,6 +3236,8 @@ pub(crate) mod tests { #[test_case(Ty::BuiltinClassLiteral("int"), Ty::BuiltinInstance("object"))] #[test_case(Ty::TypingLiteral, Ty::TypingInstance("_SpecialForm"))] #[test_case(Ty::TypingLiteral, Ty::BuiltinInstance("object"))] + #[test_case(Ty::AbcClassLiteral("ABC"), Ty::AbcInstance("ABCMeta"))] + #[test_case(Ty::AbcInstance("ABCMeta"), Ty::SubclassOfBuiltinClass("object"))] fn is_subtype_of(from: Ty, to: Ty) { let db = setup_db(); assert!(from.into_type(&db).is_subtype_of(&db, to.into_type(&db))); @@ -3244,6 +3268,9 @@ pub(crate) mod tests { #[test_case(Ty::BuiltinClassLiteral("int"), Ty::BuiltinClassLiteral("object"))] #[test_case(Ty::BuiltinInstance("int"), Ty::BuiltinClassLiteral("int"))] #[test_case(Ty::TypingInstance("_SpecialForm"), Ty::TypingLiteral)] + #[test_case(Ty::BuiltinInstance("type"), Ty::SubclassOfBuiltinClass("str"))] + #[test_case(Ty::BuiltinClassLiteral("str"), Ty::SubclassOfAny)] + #[test_case(Ty::AbcInstance("ABCMeta"), Ty::SubclassOfBuiltinClass("type"))] fn is_not_subtype_of(from: Ty, to: Ty) { let db = setup_db(); assert!(!from.into_type(&db).is_subtype_of(&db, to.into_type(&db))); @@ -3403,6 +3430,7 @@ pub(crate) mod tests { #[test_case(Ty::Intersection{pos: vec![Ty::BuiltinInstance("int"), Ty::IntLiteral(2)], neg: vec![]}, Ty::IntLiteral(2))] #[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)] fn is_not_disjoint_from(a: Ty, b: Ty) { let db = setup_db(); let a = a.into_type(&db);