From 13e7afca42e1ab4e698412df903e3dd172909caa Mon Sep 17 00:00:00 2001 From: David Peter Date: Wed, 22 Jan 2025 12:04:38 +0100 Subject: [PATCH] [red-knot] Improved error message for attribute-assignments (#15668) ## Summary Slightly improved error message for attribute assignments. --- .../resources/mdtest/attributes.md | 10 ++-- .../src/types/diagnostic.rs | 55 ++++++++++++++----- .../src/types/infer.rs | 15 +++-- 3 files changed, 56 insertions(+), 24 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/attributes.md b/crates/red_knot_python_semantic/resources/mdtest/attributes.md index 5a704decdf..8c41be1cd5 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/attributes.md +++ b/crates/red_knot_python_semantic/resources/mdtest/attributes.md @@ -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 ``` diff --git a/crates/red_knot_python_semantic/src/types/diagnostic.rs b/crates/red_knot_python_semantic/src/types/diagnostic.rs index 9699197527..a20ae364b1 100644 --- a/crates/red_knot_python_semantic/src/types/diagnostic.rs +++ b/crates/red_knot_python_semantic/src/types/diagnostic.rs @@ -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, diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 2ffae813b5..d798a136e7 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -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(), ); } }