Compare commits

...

1 Commits

Author SHA1 Message Date
Douglas Creager
9eff2734bb wip: subscript always via __getitem__ 2025-03-27 14:19:48 -04:00
4 changed files with 66 additions and 30 deletions

View File

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

View File

@@ -110,6 +110,9 @@ impl Display for DisplayRepresentation<'_> {
Type::Callable(CallableType::WrapperDescriptorDunderGet) => {
f.write_str("<wrapper-descriptor `__get__` of `function` objects>")
}
Type::Callable(CallableType::SpecializedGetitem) => {
f.write_str("<specialized `__getitem__`>")
}
Type::Union(union) => union.display(self.db).fmt(f),
Type::Intersection(intersection) => intersection.display(self.db).fmt(f),
Type::IntLiteral(n) => n.fmt(f),

View File

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

View File

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