mirror of https://github.com/astral-sh/ruff
[ty] Further improve subscript assignment diagnostics (#21411)
## Summary
Further improve subscript assignment diagnostics, especially for
`dict`s:
```py
config: dict[str, int] = {}
config["retries"] = "three"
```
<img width="1276" height="274" alt="image"
src="https://github.com/user-attachments/assets/9762c733-8d1c-4a57-8c8a-99825071dc7d"
/>
I have many more ideas, but this looks like a reasonable first step.
Thank you @AlexWaygood for some of the suggestions here.
## Test Plan
Update tests
This commit is contained in:
parent
12e74ae894
commit
04ab9170d6
|
|
@ -67,7 +67,7 @@ jobs:
|
|||
|
||||
cd ..
|
||||
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@908758da02a73ef3f3308e1dbb2248510029bbe4"
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@11aa5472cf9d6b9e019c401505a093112942d7bf"
|
||||
|
||||
ecosystem-analyzer \
|
||||
--repository ruff \
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ jobs:
|
|||
|
||||
cd ..
|
||||
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@908758da02a73ef3f3308e1dbb2248510029bbe4"
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@11aa5472cf9d6b9e019c401505a093112942d7bf"
|
||||
|
||||
ecosystem-analyzer \
|
||||
--verbose \
|
||||
|
|
|
|||
|
|
@ -19,12 +19,14 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/subscript/assignment_dia
|
|||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-assignment]: Method `__setitem__` of type `bound method dict[str, int].__setitem__(key: str, value: int, /) -> None` cannot be called with a key of type `Literal[0]` and a value of type `Literal[3]` on object of type `dict[str, int]`
|
||||
error[invalid-assignment]: Invalid subscript assignment with key of type `Literal[0]` and value of type `Literal[3]` on object of type `dict[str, int]`
|
||||
--> src/mdtest_snippet.py:2:1
|
||||
|
|
||||
1 | config: dict[str, int] = {}
|
||||
2 | config[0] = 3 # error: [invalid-assignment]
|
||||
| ^^^^^^
|
||||
| ^^^^^^^-^^^^^
|
||||
| |
|
||||
| Expected key of type `str`, got `Literal[0]`
|
||||
|
|
||||
info: rule `invalid-assignment` is enabled by default
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/subscript/assignment_dia
|
|||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-key]: Cannot access `Config` with a key of type `Literal[0]`. Only string literals are allowed as keys on TypedDicts.
|
||||
error[invalid-key]: TypedDict `Config` can only be subscripted with a string literal key, got key of type `Literal[0]`.
|
||||
--> src/mdtest_snippet.py:7:12
|
||||
|
|
||||
6 | def _(config: Config) -> None:
|
||||
|
|
|
|||
|
|
@ -19,12 +19,14 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/subscript/assignment_dia
|
|||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-assignment]: Method `__setitem__` of type `bound method dict[str, int].__setitem__(key: str, value: int, /) -> None` cannot be called with a key of type `Literal["retries"]` and a value of type `Literal["three"]` on object of type `dict[str, int]`
|
||||
error[invalid-assignment]: Invalid subscript assignment with key of type `Literal["retries"]` and value of type `Literal["three"]` on object of type `dict[str, int]`
|
||||
--> src/mdtest_snippet.py:2:1
|
||||
|
|
||||
1 | config: dict[str, int] = {}
|
||||
2 | config["retries"] = "three" # error: [invalid-assignment]
|
||||
| ^^^^^^
|
||||
| ^^^^^^^^^^^^^^^^^^^^-------
|
||||
| |
|
||||
| Expected value of type `int`, got `Literal["three"]`
|
||||
|
|
||||
info: rule `invalid-assignment` is enabled by default
|
||||
|
||||
|
|
|
|||
|
|
@ -23,13 +23,14 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/subscript/assignment_dia
|
|||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-assignment]: Cannot assign to a subscript on an object of type `ReadOnlyDict` with no `__setitem__` method
|
||||
error[invalid-assignment]: Cannot assign to a subscript on an object of type `ReadOnlyDict`
|
||||
--> src/mdtest_snippet.py:6:1
|
||||
|
|
||||
5 | config = ReadOnlyDict()
|
||||
6 | config["retries"] = 3 # error: [invalid-assignment]
|
||||
| ^^^^^^
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Consider adding a `__setitem__` method to `ReadOnlyDict`.
|
||||
info: rule `invalid-assignment` is enabled by default
|
||||
|
||||
```
|
||||
|
|
|
|||
|
|
@ -19,14 +19,15 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/subscript/assignment_dia
|
|||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-assignment]: Cannot assign to a subscript on an object of type `None` with no `__setitem__` method
|
||||
error[invalid-assignment]: Cannot assign to a subscript on an object of type `None`
|
||||
--> src/mdtest_snippet.py:2:5
|
||||
|
|
||||
1 | def _(config: dict[str, int] | None) -> None:
|
||||
2 | config["retries"] = 3 # error: [invalid-assignment]
|
||||
| ^^^^^^
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
info: The full type of the subscripted object is `dict[str, int] | None`
|
||||
info: `None` does not have a `__setitem__` method.
|
||||
info: rule `invalid-assignment` is enabled by default
|
||||
|
||||
```
|
||||
|
|
|
|||
|
|
@ -19,12 +19,14 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/subscript/assignment_dia
|
|||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-assignment]: Method `__setitem__` of type `bound method dict[str, str].__setitem__(key: str, value: str, /) -> None` cannot be called with a key of type `Literal["retries"]` and a value of type `Literal[3]` on object of type `dict[str, str]`
|
||||
error[invalid-assignment]: Invalid subscript assignment with key of type `Literal["retries"]` and value of type `Literal[3]` on object of type `dict[str, str]`
|
||||
--> src/mdtest_snippet.py:2:5
|
||||
|
|
||||
1 | def _(config: dict[str, int] | dict[str, str]) -> None:
|
||||
2 | config["retries"] = 3 # error: [invalid-assignment]
|
||||
| ^^^^^^
|
||||
| ^^^^^^^^^^^^^^^^^^^^-
|
||||
| |
|
||||
| Expected value of type `str`, got `Literal[3]`
|
||||
|
|
||||
info: The full type of the subscripted object is `dict[str, int] | dict[str, str]`
|
||||
info: rule `invalid-assignment` is enabled by default
|
||||
|
|
|
|||
|
|
@ -21,13 +21,15 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/subscript/assignment_dia
|
|||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-assignment]: Method `__setitem__` of type `bound method dict[str, int].__setitem__(key: str, value: int, /) -> None` cannot be called with a key of type `Literal["retries"]` and a value of type `float` on object of type `dict[str, int]`
|
||||
error[invalid-assignment]: Invalid subscript assignment with key of type `Literal["retries"]` and value of type `float` on object of type `dict[str, int]`
|
||||
--> src/mdtest_snippet.py:4:5
|
||||
|
|
||||
2 | # error: [invalid-assignment]
|
||||
3 | # error: [invalid-assignment]
|
||||
4 | config["retries"] = 3.0
|
||||
| ^^^^^^
|
||||
| ^^^^^^^^^^^^^^^^^^^^---
|
||||
| |
|
||||
| Expected value of type `int`, got `float`
|
||||
|
|
||||
info: The full type of the subscripted object is `dict[str, int] | dict[str, str]`
|
||||
info: rule `invalid-assignment` is enabled by default
|
||||
|
|
@ -35,13 +37,15 @@ info: rule `invalid-assignment` is enabled by default
|
|||
```
|
||||
|
||||
```
|
||||
error[invalid-assignment]: Method `__setitem__` of type `bound method dict[str, str].__setitem__(key: str, value: str, /) -> None` cannot be called with a key of type `Literal["retries"]` and a value of type `float` on object of type `dict[str, str]`
|
||||
error[invalid-assignment]: Invalid subscript assignment with key of type `Literal["retries"]` and value of type `float` on object of type `dict[str, str]`
|
||||
--> src/mdtest_snippet.py:4:5
|
||||
|
|
||||
2 | # error: [invalid-assignment]
|
||||
3 | # error: [invalid-assignment]
|
||||
4 | config["retries"] = 3.0
|
||||
| ^^^^^^
|
||||
| ^^^^^^^^^^^^^^^^^^^^---
|
||||
| |
|
||||
| Expected value of type `str`, got `float`
|
||||
|
|
||||
info: The full type of the subscripted object is `dict[str, int] | dict[str, str]`
|
||||
info: rule `invalid-assignment` is enabled by default
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ info: rule `invalid-key` is enabled by default
|
|||
```
|
||||
|
||||
```
|
||||
error[invalid-key]: Invalid key of type `str` for TypedDict `Person`
|
||||
error[invalid-key]: TypedDict `Person` can only be subscripted with string literal keys, got key of type `str`
|
||||
--> src/mdtest_snippet.py:16:12
|
||||
|
|
||||
15 | def access_with_str_key(person: Person, str_key: str):
|
||||
|
|
@ -146,7 +146,7 @@ info: rule `invalid-key` is enabled by default
|
|||
```
|
||||
|
||||
```
|
||||
error[invalid-key]: Cannot access `Person` with a key of type `str`. Only string literals are allowed as keys on TypedDicts.
|
||||
error[invalid-key]: TypedDict `Person` can only be subscripted with a string literal key, got key of type `str`.
|
||||
--> src/mdtest_snippet.py:25:12
|
||||
|
|
||||
24 | def write_to_non_literal_string_key(person: Person, str_key: str):
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ a[0] = 0
|
|||
class NoSetitem: ...
|
||||
|
||||
a = NoSetitem()
|
||||
a[0] = 0 # error: "Cannot assign to a subscript on an object of type `NoSetitem` with no `__setitem__` method"
|
||||
a[0] = 0 # error: "Cannot assign to a subscript on an object of type `NoSetitem`"
|
||||
```
|
||||
|
||||
## `__setitem__` not callable
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ def name_or_age() -> Literal["name", "age"]:
|
|||
carol: Person = {NAME: "Carol", AGE: 20}
|
||||
|
||||
reveal_type(carol[NAME]) # revealed: str
|
||||
# error: [invalid-key] "Invalid key of type `str` for TypedDict `Person`"
|
||||
# error: [invalid-key] "TypedDict `Person` can only be subscripted with string literal keys, got key of type `str`"
|
||||
reveal_type(carol[non_literal()]) # revealed: Unknown
|
||||
reveal_type(carol[name_or_age()]) # revealed: str | int | None
|
||||
|
||||
|
|
@ -553,7 +553,7 @@ def _(
|
|||
# error: [invalid-key] "Invalid key for TypedDict `Person`: Unknown key "non_existing""
|
||||
reveal_type(person["non_existing"]) # revealed: Unknown
|
||||
|
||||
# error: [invalid-key] "Invalid key of type `str` for TypedDict `Person`"
|
||||
# error: [invalid-key] "TypedDict `Person` can only be subscripted with string literal keys, got key of type `str`"
|
||||
reveal_type(person[str_key]) # revealed: Unknown
|
||||
|
||||
# No error here:
|
||||
|
|
@ -631,10 +631,10 @@ def _(person: Person, union_of_keys: Literal["name", "age"], unknown_value: Any)
|
|||
person[union_of_keys] = None
|
||||
|
||||
def _(person: Person, str_key: str, literalstr_key: LiteralString):
|
||||
# error: [invalid-key] "Cannot access `Person` with a key of type `str`. Only string literals are allowed as keys on TypedDicts."
|
||||
# error: [invalid-key] "TypedDict `Person` can only be subscripted with a string literal key, got key of type `str`."
|
||||
person[str_key] = None
|
||||
|
||||
# error: [invalid-key] "Cannot access `Person` with a key of type `LiteralString`. Only string literals are allowed as keys on TypedDicts."
|
||||
# error: [invalid-key] "TypedDict `Person` can only be subscripted with a string literal key, got key of type `LiteralString`."
|
||||
person[literalstr_key] = None
|
||||
|
||||
def _(person: Person, unknown_key: Any):
|
||||
|
|
|
|||
|
|
@ -3107,9 +3107,10 @@ pub(crate) fn report_invalid_key_on_typed_dict<'db>(
|
|||
}
|
||||
_ => {
|
||||
let mut diagnostic = builder.into_diagnostic(format_args!(
|
||||
"Invalid key of type `{}` for TypedDict `{}`",
|
||||
key_ty.display(db),
|
||||
"TypedDict `{}` can only be subscripted with string literal keys, \
|
||||
got key of type `{}`",
|
||||
typed_dict_ty.display(db),
|
||||
key_ty.display(db),
|
||||
));
|
||||
|
||||
if let Some(full_object_ty) = full_object_ty {
|
||||
|
|
|
|||
|
|
@ -3557,10 +3557,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
let slice_ty = self.infer_expression(slice, TypeContext::default());
|
||||
|
||||
self.validate_subscript_assignment_impl(
|
||||
object.as_ref(),
|
||||
target,
|
||||
None,
|
||||
object_ty,
|
||||
slice.as_ref(),
|
||||
slice_ty,
|
||||
rhs_value,
|
||||
rhs_value_ty,
|
||||
|
|
@ -3571,10 +3570,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
#[expect(clippy::too_many_arguments)]
|
||||
fn validate_subscript_assignment_impl(
|
||||
&self,
|
||||
object_node: &'ast ast::Expr,
|
||||
target: &'ast ast::ExprSubscript,
|
||||
full_object_ty: Option<Type<'db>>,
|
||||
object_ty: Type<'db>,
|
||||
slice_node: &'ast ast::Expr,
|
||||
slice_ty: Type<'db>,
|
||||
rhs_value_node: &'ast ast::Expr,
|
||||
rhs_value_ty: Type<'db>,
|
||||
|
|
@ -3602,7 +3600,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
|
||||
let db = self.db();
|
||||
|
||||
let attach_original_type_info = |mut diagnostic: LintDiagnosticGuard| {
|
||||
let attach_original_type_info = |diagnostic: &mut LintDiagnosticGuard| {
|
||||
if let Some(full_object_ty) = full_object_ty {
|
||||
diagnostic.info(format_args!(
|
||||
"The full type of the subscripted object is `{}`",
|
||||
|
|
@ -3618,10 +3616,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
let mut valid = true;
|
||||
for element_ty in union.elements(db) {
|
||||
valid &= self.validate_subscript_assignment_impl(
|
||||
object_node,
|
||||
target,
|
||||
full_object_ty.or(Some(object_ty)),
|
||||
*element_ty,
|
||||
slice_node,
|
||||
slice_ty,
|
||||
rhs_value_node,
|
||||
rhs_value_ty,
|
||||
|
|
@ -3636,10 +3633,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
let mut valid = false;
|
||||
for element_ty in intersection.positive(db) {
|
||||
valid |= self.validate_subscript_assignment_impl(
|
||||
object_node,
|
||||
target,
|
||||
full_object_ty.or(Some(object_ty)),
|
||||
*element_ty,
|
||||
slice_node,
|
||||
slice_ty,
|
||||
rhs_value_node,
|
||||
rhs_value_ty,
|
||||
|
|
@ -3676,7 +3672,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
// or a dynamic type like `Any`. We can do this by checking assignability to `LiteralString`,
|
||||
// but we need to exclude `LiteralString` itself. This check would technically allow weird key
|
||||
// types like `LiteralString & Any` to pass, but it does not need to be perfect. We would just
|
||||
// fail to provide the "Only string literals are allowed" hint in that case.
|
||||
// fail to provide the "can only be subscripted with string literal keys" hint in that case.
|
||||
|
||||
if slice_ty.is_dynamic() {
|
||||
return true;
|
||||
|
|
@ -3688,22 +3684,26 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
if slice_ty.is_assignable_to(db, Type::LiteralString)
|
||||
&& !slice_ty.is_equivalent_to(db, Type::LiteralString)
|
||||
{
|
||||
if let Some(builder) =
|
||||
self.context.report_lint(&INVALID_ASSIGNMENT, slice_node)
|
||||
if let Some(builder) = self
|
||||
.context
|
||||
.report_lint(&INVALID_ASSIGNMENT, target.slice.as_ref())
|
||||
{
|
||||
let diagnostic = builder.into_diagnostic(format_args!(
|
||||
let mut diagnostic = builder.into_diagnostic(format_args!(
|
||||
"Cannot assign value of type `{assigned_d}` to key of type `{}` on TypedDict `{value_d}`",
|
||||
slice_ty.display(db)
|
||||
));
|
||||
attach_original_type_info(diagnostic);
|
||||
attach_original_type_info(&mut diagnostic);
|
||||
}
|
||||
} else {
|
||||
if let Some(builder) = self.context.report_lint(&INVALID_KEY, slice_node) {
|
||||
let diagnostic = builder.into_diagnostic(format_args!(
|
||||
"Cannot access `{value_d}` with a key of type `{}`. Only string literals are allowed as keys on TypedDicts.",
|
||||
if let Some(builder) = self
|
||||
.context
|
||||
.report_lint(&INVALID_KEY, target.slice.as_ref())
|
||||
{
|
||||
let mut diagnostic = builder.into_diagnostic(format_args!(
|
||||
"TypedDict `{value_d}` can only be subscripted with a string literal key, got key of type `{}`.",
|
||||
slice_ty.display(db)
|
||||
));
|
||||
attach_original_type_info(diagnostic);
|
||||
attach_original_type_info(&mut diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3717,8 +3717,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
full_object_ty,
|
||||
key,
|
||||
rhs_value_ty,
|
||||
object_node,
|
||||
slice_node,
|
||||
target.value.as_ref(),
|
||||
target.slice.as_ref(),
|
||||
rhs_value_node,
|
||||
TypedDictAssignmentKind::Subscript,
|
||||
emit_diagnostic,
|
||||
|
|
@ -3741,13 +3741,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
if emit_diagnostic
|
||||
&& let Some(builder) = self
|
||||
.context
|
||||
.report_lint(&POSSIBLY_MISSING_IMPLICIT_CALL, rhs_value_node)
|
||||
.report_lint(&POSSIBLY_MISSING_IMPLICIT_CALL, target)
|
||||
{
|
||||
let diagnostic = builder.into_diagnostic(format_args!(
|
||||
let mut diagnostic = builder.into_diagnostic(format_args!(
|
||||
"Method `__setitem__` of type `{}` may be missing",
|
||||
object_ty.display(db),
|
||||
));
|
||||
attach_original_type_info(diagnostic);
|
||||
attach_original_type_info(&mut diagnostic);
|
||||
}
|
||||
false
|
||||
}
|
||||
|
|
@ -3755,17 +3755,16 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
match call_error_kind {
|
||||
CallErrorKind::NotCallable => {
|
||||
if emit_diagnostic
|
||||
&& let Some(builder) = self
|
||||
.context
|
||||
.report_lint(&CALL_NON_CALLABLE, object_node)
|
||||
&& let Some(builder) =
|
||||
self.context.report_lint(&CALL_NON_CALLABLE, target)
|
||||
{
|
||||
let diagnostic = builder.into_diagnostic(format_args!(
|
||||
let mut diagnostic = builder.into_diagnostic(format_args!(
|
||||
"Method `__setitem__` of type `{}` is not callable \
|
||||
on object of type `{}`",
|
||||
bindings.callable_type().display(db),
|
||||
object_ty.display(db),
|
||||
));
|
||||
attach_original_type_info(diagnostic);
|
||||
attach_original_type_info(&mut diagnostic);
|
||||
}
|
||||
}
|
||||
CallErrorKind::BindingError => {
|
||||
|
|
@ -3778,8 +3777,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
full_object_ty,
|
||||
key,
|
||||
rhs_value_ty,
|
||||
object_node,
|
||||
slice_node,
|
||||
target.value.as_ref(),
|
||||
target.slice.as_ref(),
|
||||
rhs_value_node,
|
||||
TypedDictAssignmentKind::Subscript,
|
||||
true,
|
||||
|
|
@ -3787,35 +3786,77 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
}
|
||||
} else {
|
||||
if emit_diagnostic
|
||||
&& let Some(builder) = self
|
||||
.context
|
||||
.report_lint(&INVALID_ASSIGNMENT, object_node)
|
||||
&& let Some(builder) = self.context.report_lint(
|
||||
&INVALID_ASSIGNMENT,
|
||||
target.range.cover(rhs_value_node.range()),
|
||||
)
|
||||
{
|
||||
let assigned_d = rhs_value_ty.display(db);
|
||||
let value_d = object_ty.display(db);
|
||||
let object_d = object_ty.display(db);
|
||||
|
||||
let diagnostic = builder.into_diagnostic(format_args!(
|
||||
// Special diagnostic for dictionaries
|
||||
if let Some([expected_key_ty, expected_value_ty]) =
|
||||
object_ty
|
||||
.known_specialization(db, KnownClass::Dict)
|
||||
.map(|s| s.types(db))
|
||||
{
|
||||
let mut diagnostic = builder.into_diagnostic(format_args!(
|
||||
"Invalid subscript assignment with key of type `{}` and value of \
|
||||
type `{assigned_d}` on object of type `{object_d}`",
|
||||
slice_ty.display(db),
|
||||
));
|
||||
|
||||
if !slice_ty.is_assignable_to(db, *expected_key_ty)
|
||||
{
|
||||
diagnostic.annotate(
|
||||
self.context
|
||||
.secondary(target.slice.as_ref())
|
||||
.message(format_args!(
|
||||
"Expected key of type `{}`, got `{}`",
|
||||
expected_key_ty.display(db),
|
||||
slice_ty.display(db),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
if !rhs_value_ty
|
||||
.is_assignable_to(db, *expected_value_ty)
|
||||
{
|
||||
diagnostic.annotate(
|
||||
self.context
|
||||
.secondary(rhs_value_node)
|
||||
.message(format_args!(
|
||||
"Expected value of type `{}`, got `{}`",
|
||||
expected_value_ty.display(db),
|
||||
rhs_value_ty.display(db),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
attach_original_type_info(&mut diagnostic);
|
||||
} else {
|
||||
let mut diagnostic = builder.into_diagnostic(format_args!(
|
||||
"Method `__setitem__` of type `{}` cannot be called with \
|
||||
a key of type `{}` and a value of type `{assigned_d}` on object of type `{value_d}`",
|
||||
a key of type `{}` and a value of type `{assigned_d}` on object of type `{object_d}`",
|
||||
bindings.callable_type().display(db),
|
||||
slice_ty.display(db),
|
||||
));
|
||||
attach_original_type_info(diagnostic);
|
||||
attach_original_type_info(&mut diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
CallErrorKind::PossiblyNotCallable => {
|
||||
if emit_diagnostic
|
||||
&& let Some(builder) = self
|
||||
.context
|
||||
.report_lint(&CALL_NON_CALLABLE, object_node)
|
||||
&& let Some(builder) =
|
||||
self.context.report_lint(&CALL_NON_CALLABLE, target)
|
||||
{
|
||||
let diagnostic = builder.into_diagnostic(format_args!(
|
||||
let mut diagnostic = builder.into_diagnostic(format_args!(
|
||||
"Method `__setitem__` of type `{}` may not be callable on object of type `{}`",
|
||||
bindings.callable_type().display(db),
|
||||
object_ty.display(db),
|
||||
));
|
||||
attach_original_type_info(diagnostic);
|
||||
attach_original_type_info(&mut diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3824,13 +3865,30 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
CallDunderError::MethodNotAvailable => {
|
||||
if emit_diagnostic
|
||||
&& let Some(builder) =
|
||||
self.context.report_lint(&INVALID_ASSIGNMENT, object_node)
|
||||
self.context.report_lint(&INVALID_ASSIGNMENT, target)
|
||||
{
|
||||
let diagnostic = builder.into_diagnostic(format_args!(
|
||||
"Cannot assign to a subscript on an object of type `{}` with no `__setitem__` method",
|
||||
let mut diagnostic = builder.into_diagnostic(format_args!(
|
||||
"Cannot assign to a subscript on an object of type `{}`",
|
||||
object_ty.display(db),
|
||||
));
|
||||
attach_original_type_info(diagnostic);
|
||||
attach_original_type_info(&mut diagnostic);
|
||||
|
||||
// Use `KnownClass` as a crude proxy for checking if this is not a user-defined class. Otherwise,
|
||||
// we end up suggesting things like "Consider adding a `__setitem__` method to `None`".
|
||||
if object_ty
|
||||
.as_nominal_instance()
|
||||
.is_some_and(|instance| instance.class(db).known(db).is_some())
|
||||
{
|
||||
diagnostic.info(format_args!(
|
||||
"`{}` does not have a `__setitem__` method.",
|
||||
object_ty.display(db),
|
||||
));
|
||||
} else {
|
||||
diagnostic.help(format_args!(
|
||||
"Consider adding a `__setitem__` method to `{}`.",
|
||||
object_ty.display(db),
|
||||
));
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue