From 9eff2734bb7907d88569d399f2b4aeff00b2aa5b Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Thu, 27 Mar 2025 14:19:48 -0400 Subject: [PATCH] wip: subscript always via __getitem__ --- crates/red_knot_python_semantic/src/types.rs | 71 ++++++++++++++----- .../src/types/display.rs | 3 + .../src/types/infer.rs | 15 +--- .../src/types/type_ordering.rs | 7 ++ 4 files changed, 66 insertions(+), 30 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 1b4d616d63..c6d52b44ed 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -659,6 +659,7 @@ impl<'db> Type<'db> { .to_instance(db) .is_subtype_of(db, target) } + (Type::Callable(CallableType::SpecializedGetitem), _) => self == target, ( Type::Callable(CallableType::General(self_callable)), @@ -1053,7 +1054,8 @@ impl<'db> Type<'db> { | Type::Callable( CallableType::BoundMethod(..) | CallableType::MethodWrapperDunderGet(..) - | CallableType::WrapperDescriptorDunderGet, + | CallableType::WrapperDescriptorDunderGet + | CallableType::SpecializedGetitem, ) | Type::ModuleLiteral(..) | Type::ClassLiteral(..) @@ -1067,7 +1069,8 @@ impl<'db> Type<'db> { | Type::Callable( CallableType::BoundMethod(..) | CallableType::MethodWrapperDunderGet(..) - | CallableType::WrapperDescriptorDunderGet, + | CallableType::WrapperDescriptorDunderGet + | CallableType::SpecializedGetitem, ) | Type::ModuleLiteral(..) | Type::ClassLiteral(..) @@ -1267,6 +1270,9 @@ impl<'db> Type<'db> { Type::Callable(CallableType::WrapperDescriptorDunderGet), ) => !KnownClass::WrapperDescriptorType.is_subclass_of(db, class), + (Type::Callable(CallableType::SpecializedGetitem), Type::Instance(_)) + | (Type::Instance(_), Type::Callable(CallableType::SpecializedGetitem)) => true, + (Type::ModuleLiteral(..), other @ Type::Instance(..)) | (other @ Type::Instance(..), Type::ModuleLiteral(..)) => { // Modules *can* actually be instances of `ModuleType` subclasses @@ -1321,7 +1327,8 @@ impl<'db> Type<'db> { | Type::Callable( CallableType::BoundMethod(_) | CallableType::MethodWrapperDunderGet(_) - | CallableType::WrapperDescriptorDunderGet, + | CallableType::WrapperDescriptorDunderGet + | CallableType::SpecializedGetitem, ) | Type::ModuleLiteral(..) | Type::IntLiteral(_) @@ -1391,7 +1398,8 @@ impl<'db> Type<'db> { | Type::Callable( CallableType::BoundMethod(_) | CallableType::MethodWrapperDunderGet(_) - | CallableType::WrapperDescriptorDunderGet, + | CallableType::WrapperDescriptorDunderGet + | CallableType::SpecializedGetitem, ) | Type::ClassLiteral(..) | Type::ModuleLiteral(..) @@ -1440,7 +1448,8 @@ impl<'db> Type<'db> { | Type::Callable( CallableType::BoundMethod(..) | CallableType::MethodWrapperDunderGet(..) - | CallableType::WrapperDescriptorDunderGet, + | CallableType::WrapperDescriptorDunderGet + | CallableType::SpecializedGetitem, ) | Type::ModuleLiteral(..) | Type::ClassLiteral(..) @@ -1513,6 +1522,7 @@ impl<'db> Type<'db> { // Hard code this knowledge, as we look up `__set__` and `__delete__` on `FunctionType` often. Some(Symbol::Unbound.into()) } + // TODO: // We currently hard-code the knowledge that the following known classes are not // descriptors, i.e. that they have no `__get__` method. This is not wrong and @@ -1652,7 +1662,8 @@ impl<'db> Type<'db> { .to_instance(db) .instance_member(db, name) } - Type::Callable(CallableType::General(_)) => { + Type::Callable(CallableType::SpecializedGetitem) + | Type::Callable(CallableType::General(_)) => { KnownClass::Object.to_instance(db).instance_member(db, name) } @@ -2006,21 +2017,31 @@ impl<'db> Type<'db> { .to_instance(db) .member(db, &name) } - Type::Callable(CallableType::General(_)) => { + Type::Callable(CallableType::SpecializedGetitem) + | Type::Callable(CallableType::General(_)) => { KnownClass::Object.to_instance(db).member(db, &name) } Type::Instance(InstanceType { class }) - if matches!(name.as_str(), "major" | "minor") + if matches!(name_str, "__getitem__" | "major" | "minor") && class.is_known(db, KnownClass::VersionInfo) => { - let python_version = Program::get(db).python_version(db); - let segment = if name == "major" { - python_version.major - } else { - python_version.minor - }; - Symbol::bound(Type::IntLiteral(segment.into())).into() + match name_str { + "__getitem__" => { + Symbol::bound(Type::Callable(CallableType::SpecializedGetitem)).into() + } + "major" => { + let python_version = Program::get(db).python_version(db); + let segment = python_version.major; + Symbol::bound(Type::IntLiteral(segment.into())).into() + } + "minor" => { + let python_version = Program::get(db).python_version(db); + let segment = python_version.minor; + Symbol::bound(Type::IntLiteral(segment.into())).into() + } + _ => unreachable!(), + } } Type::IntLiteral(_) if matches!(name_str, "real" | "numerator") => { @@ -2455,6 +2476,20 @@ impl<'db> Type<'db> { Signatures::single(signature) } + Type::Callable(CallableType::SpecializedGetitem) => { + let signature = CallableSignature::from_overloads( + self, + [Signature::new( + Parameters::new([Parameter::positional_only(Some(Name::new_static( + "slice", + ))) + .with_annotated_type(KnownClass::Int.to_instance(db))]), + None, + )], + ); + Signatures::single(signature) + } + Type::FunctionLiteral(function_type) => match function_type.known(db) { Some( KnownFunction::IsEquivalentTo @@ -3170,7 +3205,8 @@ impl<'db> Type<'db> { Type::Callable(CallableType::WrapperDescriptorDunderGet) => { KnownClass::WrapperDescriptorType.to_class_literal(db) } - Type::Callable(CallableType::General(_)) => KnownClass::Type.to_instance(db), + Type::Callable(CallableType::SpecializedGetitem) + | Type::Callable(CallableType::General(_)) => KnownClass::Type.to_instance(db), Type::ModuleLiteral(_) => KnownClass::ModuleType.to_class_literal(db), Type::Tuple(_) => KnownClass::Tuple.to_class_literal(db), Type::ClassLiteral(ClassLiteralType { class }) => class.metaclass(db), @@ -4857,6 +4893,9 @@ pub enum CallableType<'db> { /// type. We currently add this as a separate variant because `FunctionType.__get__` /// is an overloaded method and we do not support `@overload` yet. WrapperDescriptorDunderGet, + + /// Represents specialized `__getitem__` methods + SpecializedGetitem, } #[salsa::interned(debug)] diff --git a/crates/red_knot_python_semantic/src/types/display.rs b/crates/red_knot_python_semantic/src/types/display.rs index 3d4fb84c8c..d371eae93e 100644 --- a/crates/red_knot_python_semantic/src/types/display.rs +++ b/crates/red_knot_python_semantic/src/types/display.rs @@ -110,6 +110,9 @@ impl Display for DisplayRepresentation<'_> { Type::Callable(CallableType::WrapperDescriptorDunderGet) => { f.write_str("") } + Type::Callable(CallableType::SpecializedGetitem) => { + f.write_str("") + } Type::Union(union) => union.display(self.db).fmt(f), Type::Intersection(intersection) => intersection.display(self.db).fmt(f), Type::IntLiteral(n) => n.fmt(f), diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 35f7db9202..bf98dbb870 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -5543,20 +5543,6 @@ impl<'db> TypeInferenceBuilder<'db> { slice_ty: Type<'db>, ) -> Type<'db> { match (value_ty, slice_ty) { - ( - Type::Instance(instance), - Type::IntLiteral(_) | Type::BooleanLiteral(_) | Type::SliceLiteral(_), - ) if instance - .class() - .is_known(self.db(), KnownClass::VersionInfo) => - { - self.infer_subscript_expression_types( - value_node, - Type::version_info_tuple(self.db()), - slice_ty, - ) - } - // Ex) Given `("a", "b", "c", "d")[1]`, return `"b"` (Type::Tuple(tuple_ty), Type::IntLiteral(int)) if i32::try_from(int).is_ok() => { let elements = tuple_ty.elements(self.db()); @@ -5698,6 +5684,7 @@ impl<'db> TypeInferenceBuilder<'db> { return err.fallback_return_type(self.db()); } Err(CallDunderError::CallError(_, bindings)) => { + bindings.report_diagnostics(&self.context, value_node.into()); self.context.report_lint( &CALL_NON_CALLABLE, value_node, diff --git a/crates/red_knot_python_semantic/src/types/type_ordering.rs b/crates/red_knot_python_semantic/src/types/type_ordering.rs index c80bd55170..9b53bb67db 100644 --- a/crates/red_knot_python_semantic/src/types/type_ordering.rs +++ b/crates/red_knot_python_semantic/src/types/type_ordering.rs @@ -83,6 +83,13 @@ pub(super) fn union_or_intersection_elements_ordering<'db>( (Type::Callable(CallableType::WrapperDescriptorDunderGet), _) => Ordering::Less, (_, Type::Callable(CallableType::WrapperDescriptorDunderGet)) => Ordering::Greater, + ( + Type::Callable(CallableType::SpecializedGetitem), + Type::Callable(CallableType::SpecializedGetitem), + ) => Ordering::Equal, + (Type::Callable(CallableType::SpecializedGetitem), _) => Ordering::Less, + (_, Type::Callable(CallableType::SpecializedGetitem)) => Ordering::Greater, + (Type::Callable(CallableType::General(_)), Type::Callable(CallableType::General(_))) => { Ordering::Equal }