[red-knot] Improved error message for attribute-assignments (#15668)

## Summary

Slightly improved error message for attribute assignments.
This commit is contained in:
David Peter 2025-01-22 12:04:38 +01:00 committed by GitHub
parent f349dab4fc
commit 13e7afca42
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 56 additions and 24 deletions

View file

@ -102,7 +102,7 @@ reveal_type(C.pure_instance_variable) # revealed: str
# and pyright allow this.
C.pure_instance_variable = "overwritten on class"
# error: [invalid-assignment] "Object of type `Literal[1]` is not assignable to `str`"
# error: [invalid-assignment] "Object of type `Literal[1]` is not assignable to attribute `pure_instance_variable` of type `str`"
c_instance.pure_instance_variable = 1
```
@ -191,7 +191,7 @@ c_instance.pure_class_variable1 = "value set on instance"
C.pure_class_variable1 = "overwritten on class"
# error: [invalid-assignment] "Object of type `Literal[1]` is not assignable to `str`"
# error: [invalid-assignment] "Object of type `Literal[1]` is not assignable to attribute `pure_class_variable1` of type `str`"
C.pure_class_variable1 = 1
class Subclass(C):
@ -448,10 +448,10 @@ import mod
reveal_type(mod.global_symbol) # revealed: str
mod.global_symbol = "b"
# error: [invalid-assignment] "Object of type `Literal[1]` is not assignable to `str`"
# error: [invalid-assignment] "Object of type `Literal[1]` is not assignable to attribute `global_symbol` of type `str`"
mod.global_symbol = 1
# error: [invalid-assignment] "Object of type `Literal[1]` is not assignable to `str`"
# error: [invalid-assignment] "Object of type `Literal[1]` is not assignable to attribute `global_symbol` of type `str`"
(_, mod.global_symbol) = (..., 1)
# TODO: this should be an error, but we do not understand list unpackings yet.
@ -465,7 +465,7 @@ class IntIterable:
def __iter__(self) -> IntIterator:
return IntIterator()
# error: [invalid-assignment] "Object of type `int` is not assignable to `str`"
# error: [invalid-assignment] "Object of type `int` is not assignable to attribute `global_symbol` of type `str`"
for mod.global_symbol in IntIterable():
pass
```

View file

@ -984,13 +984,13 @@ pub(super) fn report_slice_step_size_zero(context: &InferContext, node: AnyNodeR
);
}
pub(super) fn report_invalid_assignment(
fn report_invalid_assignment_with_message(
context: &InferContext,
node: AnyNodeRef,
declared_ty: Type,
assigned_ty: Type,
target_ty: Type,
message: std::fmt::Arguments,
) {
match declared_ty {
match target_ty {
Type::ClassLiteral(ClassLiteralType { class }) => {
context.report_lint(&INVALID_ASSIGNMENT, node, format_args!(
"Implicit shadowing of class `{}`; annotate to make it explicit if this is intentional",
@ -1002,19 +1002,48 @@ pub(super) fn report_invalid_assignment(
function.name(context.db())));
}
_ => {
context.report_lint(
&INVALID_ASSIGNMENT,
node,
format_args!(
"Object of type `{}` is not assignable to `{}`",
assigned_ty.display(context.db()),
declared_ty.display(context.db()),
),
);
context.report_lint(&INVALID_ASSIGNMENT, node, message);
}
}
}
pub(super) fn report_invalid_assignment(
context: &InferContext,
node: AnyNodeRef,
target_ty: Type,
source_ty: Type,
) {
report_invalid_assignment_with_message(
context,
node,
target_ty,
format_args!(
"Object of type `{}` is not assignable to `{}`",
source_ty.display(context.db()),
target_ty.display(context.db()),
),
);
}
pub(super) fn report_invalid_attribute_assignment(
context: &InferContext,
node: AnyNodeRef,
target_ty: Type,
source_ty: Type,
attribute_name: &'_ str,
) {
report_invalid_assignment_with_message(
context,
node,
target_ty,
format_args!(
"Object of type `{}` is not assignable to attribute `{attribute_name}` of type `{}`",
source_ty.display(context.db()),
target_ty.display(context.db()),
),
);
}
pub(super) fn report_possibly_unresolved_reference(
context: &InferContext,
expr_name_node: &ast::ExprName,

View file

@ -51,11 +51,12 @@ use crate::semantic_index::SemanticIndex;
use crate::stdlib::builtins_module_scope;
use crate::types::call::{Argument, CallArguments};
use crate::types::diagnostic::{
report_invalid_arguments_to_annotated, report_invalid_assignment, report_unresolved_module,
TypeCheckDiagnostics, CALL_NON_CALLABLE, CALL_POSSIBLY_UNBOUND_METHOD,
CONFLICTING_DECLARATIONS, CONFLICTING_METACLASS, CYCLIC_CLASS_DEFINITION, DIVISION_BY_ZERO,
DUPLICATE_BASE, INCONSISTENT_MRO, INVALID_ATTRIBUTE_ACCESS, INVALID_BASE,
INVALID_CONTEXT_MANAGER, INVALID_DECLARATION, INVALID_PARAMETER_DEFAULT, INVALID_TYPE_FORM,
report_invalid_arguments_to_annotated, report_invalid_assignment,
report_invalid_attribute_assignment, report_unresolved_module, TypeCheckDiagnostics,
CALL_NON_CALLABLE, CALL_POSSIBLY_UNBOUND_METHOD, CONFLICTING_DECLARATIONS,
CONFLICTING_METACLASS, CYCLIC_CLASS_DEFINITION, DIVISION_BY_ZERO, DUPLICATE_BASE,
INCONSISTENT_MRO, INVALID_ATTRIBUTE_ACCESS, INVALID_BASE, INVALID_CONTEXT_MANAGER,
INVALID_DECLARATION, INVALID_PARAMETER_DEFAULT, INVALID_TYPE_FORM,
INVALID_TYPE_VARIABLE_CONSTRAINTS, POSSIBLY_UNBOUND_ATTRIBUTE, POSSIBLY_UNBOUND_IMPORT,
UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_IMPORT, UNSUPPORTED_OPERATOR,
};
@ -2022,6 +2023,7 @@ impl<'db> TypeInferenceBuilder<'db> {
ast::Expr::Attribute(
lhs_expr @ ast::ExprAttribute {
ctx: ExprContext::Store,
attr,
..
},
) => {
@ -2030,11 +2032,12 @@ impl<'db> TypeInferenceBuilder<'db> {
if let Some(assigned_ty) = assigned_ty {
if !assigned_ty.is_assignable_to(self.db(), attribute_expr_ty) {
report_invalid_assignment(
report_invalid_attribute_assignment(
&self.context,
target.into(),
attribute_expr_ty,
assigned_ty,
attr.as_str(),
);
}
}