diff --git a/crates/ty_python_semantic/resources/mdtest/narrow/assignment.md b/crates/ty_python_semantic/resources/mdtest/narrow/assignment.md index 3ba2200598..b204b4b270 100644 --- a/crates/ty_python_semantic/resources/mdtest/narrow/assignment.md +++ b/crates/ty_python_semantic/resources/mdtest/narrow/assignment.md @@ -253,7 +253,6 @@ does["not"]["exist"] = 0 reveal_type(does["not"]["exist"]) # revealed: Unknown non_subscriptable = 1 -# error: [non-subscriptable] non_subscriptable[0] = 0 # error: [non-subscriptable] reveal_type(non_subscriptable[0]) # revealed: Unknown diff --git a/crates/ty_python_semantic/resources/mdtest/subscript/instance.md b/crates/ty_python_semantic/resources/mdtest/subscript/instance.md index 19a4aac66e..94aa44eea2 100644 --- a/crates/ty_python_semantic/resources/mdtest/subscript/instance.md +++ b/crates/ty_python_semantic/resources/mdtest/subscript/instance.md @@ -1,6 +1,6 @@ # Instance subscript -## Getitem unbound +## `__getitem__` unbound ```py class NotSubscriptable: ... @@ -8,7 +8,7 @@ class NotSubscriptable: ... a = NotSubscriptable()[0] # error: "Cannot subscript object of type `NotSubscriptable` with no `__getitem__` method" ``` -## Getitem not callable +## `__getitem__` not callable ```py class NotSubscriptable: @@ -18,7 +18,7 @@ class NotSubscriptable: a = NotSubscriptable()[0] ``` -## Valid getitem +## Valid `__getitem__` ```py class Identity: @@ -28,7 +28,7 @@ class Identity: reveal_type(Identity()[0]) # revealed: int ``` -## Getitem union +## `__getitem__` union ```py def _(flag: bool): @@ -42,3 +42,14 @@ def _(flag: bool): reveal_type(Identity()[0]) # revealed: int | str ``` + +## `__setitem__` with no `__getitem__` + +```py +class NoGetitem: + def __setitem__(self, index: int, value: int) -> None: + pass + +a = NoGetitem() +a[0] = 0 +``` diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 064f907edc..9f3b634e3c 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -1900,13 +1900,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } else if let AnyNodeRef::ExprSubscript(ast::ExprSubscript { value, slice, + ctx, .. }) = node { let value_ty = self.infer_expression(value); let slice_ty = self.infer_expression(slice); - let result_ty = - self.infer_subscript_expression_types(value, value_ty, slice_ty); + let result_ty = self + .infer_subscript_expression_types(value, value_ty, slice_ty, *ctx); return (result_ty, is_modifiable); } } @@ -8291,7 +8292,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { ExprContext::Store => { let value_ty = self.infer_expression(value); let slice_ty = self.infer_expression(slice); - self.infer_subscript_expression_types(value, value_ty, slice_ty); + self.infer_subscript_expression_types(value, value_ty, slice_ty, *ctx); Type::Never } ExprContext::Del => { @@ -8301,7 +8302,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { ExprContext::Invalid => { let value_ty = self.infer_expression(value); let slice_ty = self.infer_expression(slice); - self.infer_subscript_expression_types(value, value_ty, slice_ty); + self.infer_subscript_expression_types(value, value_ty, slice_ty, *ctx); Type::unknown() } } @@ -8313,7 +8314,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { node_index: _, value, slice, - ctx: _, + ctx, } = subscript; let value_ty = self.infer_expression(value); let mut constraint_keys = vec![]; @@ -8330,7 +8331,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { // Even if we can obtain the subscript type based on the assignments, we still perform default type inference // (to store the expression type and to report errors). let slice_ty = self.infer_expression(slice); - self.infer_subscript_expression_types(value, value_ty, slice_ty); + self.infer_subscript_expression_types(value, value_ty, slice_ty, *ctx); return ty; } } @@ -8364,7 +8365,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } let slice_ty = self.infer_expression(slice); - let result_ty = self.infer_subscript_expression_types(value, value_ty, slice_ty); + let result_ty = self.infer_subscript_expression_types(value, value_ty, slice_ty, *ctx); self.narrow_expr_with_applicable_constraints(subscript, result_ty, &constraint_keys) } @@ -8418,6 +8419,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { value_node: &ast::Expr, value_ty: Type<'db>, slice_ty: Type<'db>, + expr_context: ExprContext, ) -> Type<'db> { match (value_ty, slice_ty, slice_ty.slice_literal(self.db())) { (Type::NominalInstance(instance), _, _) @@ -8427,11 +8429,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { value_node, Type::version_info_tuple(self.db()), slice_ty, + expr_context, ) } (Type::Union(union), _, _) => union.map(self.db(), |element| { - self.infer_subscript_expression_types(value_node, *element, slice_ty) + self.infer_subscript_expression_types(value_node, *element, slice_ty, expr_context) }), // TODO: we can map over the intersection and fold the results back into an intersection, @@ -8562,6 +8565,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { value_node, value_ty, Type::IntLiteral(i64::from(bool)), + expr_context, ), (Type::SpecialForm(SpecialFormType::Protocol), Type::Tuple(typevars), _) => { @@ -8754,12 +8758,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { ); } } else { - report_non_subscriptable( - &self.context, - value_node.into(), - value_ty, - "__getitem__", - ); + if expr_context != ExprContext::Store { + report_non_subscriptable( + &self.context, + value_node.into(), + value_ty, + "__getitem__", + ); + } } Type::unknown()