From 66360a0fba5cee60c6ff20e679fdc33b4f91a36b Mon Sep 17 00:00:00 2001 From: Jingchen Ye <11172084+97littleleaf11@users.noreply.github.com> Date: Sat, 13 May 2023 01:31:34 +0800 Subject: [PATCH 1/6] Initial implementation Co-authored-by: ucodery --- mypy/checker.py | 111 ++++++++++++++---- mypy/checkexpr.py | 4 +- mypy/message_registry.py | 3 + test-data/unit/check-classes.test | 8 +- .../unit/check-multiple-inheritance.test | 18 +-- test-data/unit/check-type-aliases.test | 4 +- test-data/unit/fine-grained.test | 10 +- 7 files changed, 114 insertions(+), 44 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index a52e8956145d0..075538b91c068 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1392,8 +1392,8 @@ def check_default_args(self, item: FuncItem, body_is_trivial: bool) -> None: arg.initializer, context=arg.initializer, msg=ErrorMessage(msg, code=codes.ASSIGNMENT), - lvalue_name="argument", - rvalue_name="default", + lvalue_name="argument has type", + rvalue_name="default has type", notes=notes, ) @@ -2645,8 +2645,8 @@ def check_import(self, node: ImportBase) -> None: assign.rvalue, node, msg=message, - lvalue_name="local name", - rvalue_name="imported name", + lvalue_name="local name has type", + rvalue_name="imported name has type", ) # @@ -2856,7 +2856,7 @@ def check_assignment( ): # Ignore member access to modules instance_type = self.expr_checker.accept(lvalue.expr) rvalue_type, lvalue_type, infer_lvalue_type = self.check_member_assignment( - instance_type, lvalue_type, rvalue, context=rvalue + instance_type, lvalue_type, lvalue, rvalue, context=rvalue ) else: # Hacky special case for assigning a literal None @@ -3113,7 +3113,8 @@ def check_compatibility_super( rvalue, message_registry.INCOMPATIBLE_TYPES_IN_ASSIGNMENT, "expression has type", - f'base class "{base.name}" defined the type as', + message_registry.INCOMPATIBLE_TYPES_BASECLASS_MISMATCH.format(base.name), + # f'base class "{base.name}" defined the type as', ) return True @@ -3924,8 +3925,8 @@ def check_simple_assignment( rvalue: Expression, context: Context, msg: ErrorMessage = message_registry.INCOMPATIBLE_TYPES_IN_ASSIGNMENT, - lvalue_name: str = "variable", - rvalue_name: str = "expression", + lvalue_name: str = "variable has type", + rvalue_name: str = "expression has type", *, notes: list[str] | None = None, ) -> Type: @@ -3974,14 +3975,19 @@ def check_simple_assignment( lvalue_type, context, msg, - f"{rvalue_name} has type", - f"{lvalue_name} has type", + rvalue_name, + lvalue_name, notes=notes, ) return rvalue_type def check_member_assignment( - self, instance_type: Type, attribute_type: Type, rvalue: Expression, context: Context + self, + instance_type: Type, + attribute_type: Type, + lvalue: MemberExpr, + rvalue: Expression, + context: Context, ) -> tuple[Type, Type, bool]: """Type member assignment. @@ -3997,15 +4003,20 @@ def check_member_assignment( instance_type = get_proper_type(instance_type) attribute_type = get_proper_type(attribute_type) # Descriptors don't participate in class-attribute access - if (isinstance(instance_type, FunctionLike) and instance_type.is_type_obj()) or isinstance( - instance_type, TypeType - ): - rvalue_type = self.check_simple_assignment(attribute_type, rvalue, context) + if isinstance(instance_type, CallableType) and instance_type.is_type_obj(): + rvalue_type = self.check_parent_member_assignment( + instance_type.ret_type, attribute_type, lvalue, rvalue + ) + return rvalue_type, attribute_type, True + elif isinstance(instance_type, TypeType): + rvalue_type = self.check_parent_member_assignment( + instance_type.item, attribute_type, lvalue, rvalue + ) return rvalue_type, attribute_type, True if not isinstance(attribute_type, Instance): # TODO: support __set__() for union types. - rvalue_type = self.check_simple_assignment(attribute_type, rvalue, context) + rvalue_type = self.check_simple_assignment(attribute_type, rvalue, context=context) return rvalue_type, attribute_type, True mx = MemberContext( @@ -4024,7 +4035,9 @@ def check_member_assignment( # the return type of __get__. This doesn't match the python semantics, # (which allow you to override the descriptor with any value), but preserves # the type of accessing the attribute (even after the override). - rvalue_type = self.check_simple_assignment(get_type, rvalue, context) + rvalue_type = self.check_parent_member_assignment( + instance_type, get_type, lvalue, rvalue + ) return rvalue_type, get_type, True dunder_set = attribute_type.type.get_method("__set__") @@ -4033,7 +4046,7 @@ def check_member_assignment( message_registry.DESCRIPTOR_SET_NOT_CALLABLE.format( attribute_type.str_with_options(self.options) ), - context, + context=context, ) return AnyType(TypeOfAny.from_error), get_type, False @@ -4054,7 +4067,7 @@ def check_member_assignment( dunder_set_type, [TempNode(instance_type, context=context), rvalue], [nodes.ARG_POS, nodes.ARG_POS], - context, + context=context, object_type=attribute_type, ) @@ -4066,7 +4079,7 @@ def check_member_assignment( dunder_set_type, [TempNode(instance_type, context=context), type_context], [nodes.ARG_POS, nodes.ARG_POS], - context, + context=context, object_type=attribute_type, callable_name=callable_name, ) @@ -4080,7 +4093,7 @@ def check_member_assignment( dunder_set_type, [TempNode(instance_type, context=context), type_context], [nodes.ARG_POS, nodes.ARG_POS], - context, + context=context, object_type=attribute_type, callable_name=callable_name, ) @@ -4096,10 +4109,64 @@ def check_member_assignment( # and '__get__' type is narrower than '__set__', then we invoke the binder to narrow type # by this assignment. Technically, this is not safe, but in practice this is # what a user expects. - rvalue_type = self.check_simple_assignment(set_type, rvalue, context) + rvalue_type = self.check_simple_assignment(set_type, rvalue, context=context) infer = is_subtype(rvalue_type, get_type) and is_subtype(get_type, set_type) return rvalue_type if infer else set_type, get_type, infer + def check_parent_member_assignment( + self, + lvalue_base_type: Type, + lvalue_type: Type, + lvalue: MemberExpr, + rvalue: Expression, + ) -> Type: + if ( + isinstance(lvalue_base_type, Instance) + and lvalue.name not in lvalue_base_type.type.names + ): + lvalue_warn = message_registry.INCOMPATIBLE_TYPES_BASECLASS_MISMATCH + metaclass = lvalue_base_type.type.metaclass_type + if metaclass and metaclass.type.has_readable_member(lvalue.name): + if lvalue.name in metaclass.type.names: + meta_attribute = metaclass.type.names[lvalue.name].node + if not (isinstance(meta_attribute, Var) and meta_attribute.info.is_generic()): + return self.check_simple_assignment( + lvalue_type, + rvalue, + context=rvalue, + msg=message_registry.INCOMPATIBLE_TYPES_IN_ASSIGNMENT, + lvalue_name=message_registry.INCOMPATIBLE_TYPES_METACLASS_MISMATCH.format( + metaclass.type.defn.name + ), + ) + else: + # attribute is defined further up in the metaclass MRO + lvalue_warn = 'base class "{{}}" of {}'.format( + message_registry.INCOMPATIBLE_TYPES_METACLASS_MISMATCH.format( + metaclass.type.defn.name + ) + ) + lvalue_base_type = metaclass + if lvalue_base_type.type.has_readable_member(lvalue.name): + for base in lvalue_base_type.type.bases: + if lvalue.name in base.type.names: + parent_context = base.type.names[lvalue.name].node + if ( + isinstance(parent_context, Var) + and parent_context.info.is_generic() + ): + # Attribute may have been defined in this class but its type is + # probably defined elsewhere, possibly in the instance itself. + continue + return self.check_simple_assignment( + lvalue_type, + rvalue, + context=rvalue, + msg=message_registry.INCOMPATIBLE_TYPES_IN_ASSIGNMENT, + lvalue_name=lvalue_warn.format(base.type.defn.name), + ) + return self.check_simple_assignment(lvalue_type, rvalue, context=rvalue) + def check_indexed_assignment( self, lvalue: IndexExpr, rvalue: Expression, context: Context ) -> None: diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index f9fbd53866da5..29b4eed780945 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -852,8 +852,8 @@ def check_typeddict_call_with_kwargs( msg=ErrorMessage( message_registry.INCOMPATIBLE_TYPES.value, code=codes.TYPEDDICT_ITEM ), - lvalue_name=f'TypedDict item "{item_name}"', - rvalue_name="expression", + lvalue_name=f'TypedDict item "{item_name}" has type', + rvalue_name="expression has type", ) return orig_ret_type diff --git a/mypy/message_registry.py b/mypy/message_registry.py index b32edc06571a8..4fe1ec9fe0c4e 100644 --- a/mypy/message_registry.py +++ b/mypy/message_registry.py @@ -73,6 +73,9 @@ def with_additional_msg(self, info: str) -> ErrorMessage: 'Incompatible types in "async with" for "__aexit__"' ) INCOMPATIBLE_TYPES_IN_ASYNC_FOR: Final = 'Incompatible types in "async for"' +INCOMPATIBLE_TYPES_BASECLASS_MISMATCH: Final = 'base class "{}" defined the type as' +INCOMPATIBLE_TYPES_METACLASS_MISMATCH: Final = 'metaclass "{}" defined the type as' + INVALID_TYPE_FOR_SLOTS: Final = 'Invalid type for "__slots__"' ASYNC_FOR_OUTSIDE_COROUTINE: Final = '"async for" outside async function' diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 6476ad1566dcd..441b30bdd052c 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -234,7 +234,7 @@ class A: self.x = 0 class B(A): def f(self) -> None: - self.x = '' # E: Incompatible types in assignment (expression has type "str", variable has type "int") + self.x = '' # E: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int") [targets __main__, __main__.A.f, __main__.B.f] [case testAssignmentToAttributeInMultipleMethods] @@ -4132,7 +4132,7 @@ class B(A): def __init__(self) -> None: self.a = "a" [out] -main:5: error: Incompatible types in assignment (expression has type "str", variable has type "int") +main:5: error: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int") [case testVariableSubclassTypeOverwrite] class A: @@ -6704,7 +6704,7 @@ from b import C class D(C): def g(self) -> None: - self.x = '' # E: Incompatible types in assignment (expression has type "str", variable has type "int") + self.x = '' # E: Incompatible types in assignment (expression has type "str", base class "C" defined the type as "int") def f(self) -> None: reveal_type(self.x) # N: Revealed type is "builtins.int" @@ -6735,7 +6735,7 @@ class C: class E(C): def g(self) -> None: - self.x = '' # E: Incompatible types in assignment (expression has type "str", variable has type "int") + self.x = '' # E: Incompatible types in assignment (expression has type "str", base class "C" defined the type as "int") def f(self) -> None: reveal_type(self.x) # N: Revealed type is "builtins.int" diff --git a/test-data/unit/check-multiple-inheritance.test b/test-data/unit/check-multiple-inheritance.test index d03f2e35e1c46..cff4279e70349 100644 --- a/test-data/unit/check-multiple-inheritance.test +++ b/test-data/unit/check-multiple-inheritance.test @@ -44,9 +44,9 @@ class B: class C(A, B): pass c = C() c.x = 1 -c.x = '' # E: Incompatible types in assignment (expression has type "str", variable has type "int") +c.x = '' # E: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int") c.y = '' -c.y = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "str") +c.y = 1 # E: Incompatible types in assignment (expression has type "int", base class "B" defined the type as "str") [case testSimpleMultipleInheritanceAndInstanceVariableInClassBody] import typing @@ -57,9 +57,9 @@ class B: class C(A, B): pass c = C() c.x = 1 -c.x = '' # E: Incompatible types in assignment (expression has type "str", variable has type "int") +c.x = '' # E: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int") c.y = '' -c.y = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "str") +c.y = 1 # E: Incompatible types in assignment (expression has type "int", base class "B" defined the type as "str") [case testSimpleMultipleInheritanceAndClassVariable] import typing @@ -69,9 +69,9 @@ class B: y = '' class C(A, B): pass C.x = 1 -C.x = '' # E: Incompatible types in assignment (expression has type "str", variable has type "int") +C.x = '' # E: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int") C.y = '' -C.y = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "str") +C.y = 1 # E: Incompatible types in assignment (expression has type "int", base class "B" defined the type as "str") -- Name collisions @@ -100,7 +100,7 @@ class B: class C(A, B): pass c = C() c.x = 1 -c.x = '' # E: Incompatible types in assignment (expression has type "str", variable has type "int") +c.x = '' # E: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int") [case testClassVarNameOverlapInMultipleInheritanceWithCompatibleTypes] import typing @@ -111,9 +111,9 @@ class B: class C(A, B): pass c = C() c.x = 1 -c.x = '' # E: Incompatible types in assignment (expression has type "str", variable has type "int") +c.x = '' # E: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int") C.x = 1 -C.x = '' # E: Incompatible types in assignment (expression has type "str", variable has type "int") +C.x = '' # E: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int") [case testMethodNameCollisionInMultipleInheritanceWithIncompatibleSigs] import typing diff --git a/test-data/unit/check-type-aliases.test b/test-data/unit/check-type-aliases.test index 05a03ecaf7b09..ea25d1b93cbcb 100644 --- a/test-data/unit/check-type-aliases.test +++ b/test-data/unit/check-type-aliases.test @@ -934,8 +934,8 @@ Parent.NormalExplicit = 4 # E: Incompatible types in assignment (expression has Parent.SpecialImplicit = 4 # E: Incompatible types in assignment (expression has type "int", variable has type "") Parent.SpecialExplicit = 4 # E: Incompatible types in assignment (expression has type "int", variable has type "") -Child.NormalImplicit = 4 # E: Incompatible types in assignment (expression has type "int", variable has type "Type[Foo]") -Child.NormalExplicit = 4 # E: Incompatible types in assignment (expression has type "int", variable has type "Type[Foo]") +Child.NormalImplicit = 4 # E: Incompatible types in assignment (expression has type "int", base class "Parent" defined the type as "Type[Foo]") +Child.NormalExplicit = 4 # E: Incompatible types in assignment (expression has type "int", base class "Parent" defined the type as "Type[Foo]") Child.SpecialImplicit = 4 Child.SpecialExplicit = 4 diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index 88a11be31f340..7be7a17b46bf2 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -1106,7 +1106,7 @@ class A: [out] == main:6: error: Incompatible types in assignment (expression has type "str", variable has type "int") -main:9: error: Incompatible types in assignment (expression has type "int", variable has type "str") +main:9: error: Incompatible types in assignment (expression has type "int", base class "A" defined the type as "str") main:14: error: Incompatible types in assignment (expression has type "str", variable has type "int") [case testChangeBaseClassAttributeType] @@ -1124,7 +1124,7 @@ class A: self.x = 'a' [out] == -main:4: error: Incompatible types in assignment (expression has type "int", variable has type "str") +main:4: error: Incompatible types in assignment (expression has type "int", base class "A" defined the type as "str") [case testRemoveAttributeInBaseClass] import m @@ -2845,7 +2845,7 @@ class M(type): x: int [out] == -a.py:4: error: Incompatible types in assignment (expression has type "int", variable has type "str") +a.py:4: error: Incompatible types in assignment (expression has type "int", metaclass "M" defined the type as "str") == a.py:4: error: "Type[C]" has no attribute "x" == @@ -2874,7 +2874,7 @@ class M(type): x: int [out] == -a.py:3: error: Incompatible types in assignment (expression has type "int", variable has type "str") +a.py:3: error: Incompatible types in assignment (expression has type "int", metaclass "M" defined the type as "str") == a.py:3: error: "Type[C]" has no attribute "x" == @@ -4425,7 +4425,7 @@ class C: y: str [out] == -main:4: error: Incompatible types in assignment (expression has type "int", variable has type "str") +main:4: error: Incompatible types in assignment (expression has type "int", base class "C" defined the type as "str") [case testNewTypeDependencies2] from a import N From e39051bff4fb52dd006f046801a7a691de98ec1f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 12 May 2023 19:49:54 +0000 Subject: [PATCH 2/6] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/checker.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 075538b91c068..aeb77a09a5e12 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -4114,11 +4114,7 @@ def check_member_assignment( return rvalue_type if infer else set_type, get_type, infer def check_parent_member_assignment( - self, - lvalue_base_type: Type, - lvalue_type: Type, - lvalue: MemberExpr, - rvalue: Expression, + self, lvalue_base_type: Type, lvalue_type: Type, lvalue: MemberExpr, rvalue: Expression ) -> Type: if ( isinstance(lvalue_base_type, Instance) @@ -4151,10 +4147,7 @@ def check_parent_member_assignment( for base in lvalue_base_type.type.bases: if lvalue.name in base.type.names: parent_context = base.type.names[lvalue.name].node - if ( - isinstance(parent_context, Var) - and parent_context.info.is_generic() - ): + if isinstance(parent_context, Var) and parent_context.info.is_generic(): # Attribute may have been defined in this class but its type is # probably defined elsewhere, possibly in the instance itself. continue From 5c71bcae95dfbadb6ef371360e44079dc1857cea Mon Sep 17 00:00:00 2001 From: Jingchen Ye <11172084+97littleleaf11@users.noreply.github.com> Date: Sat, 13 May 2023 04:22:54 +0800 Subject: [PATCH 3/6] Add tests Co-authored-by: ucodery --- test-data/unit/check-classes.test | 33 +++++++++++++++++++++++ test-data/unit/check-functions.test | 41 +++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 441b30bdd052c..271c367fd6027 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -7859,3 +7859,36 @@ f5(1) # E: Argument 1 to "f5" has incompatible type "int"; expected "Integral" # N: Types from "numbers" aren't supported for static type checking \ # N: See https://peps.python.org/pep-0484/#the-numeric-tower \ # N: Consider using a protocol instead, such as typing.SupportsFloat + +[case testMetaclassParentAttribute] +import submod +class A(metaclass=submod.M): + pass +A.x = "" +A.x = 1 +[file submod.py] +class N(type): + x: str +class M(N): + pass +[out] +main:5: error: Incompatible types in assignment (expression has type "int", base class "N" of metaclass "M" defined the type as "str") + +[case testParentMetaclassAttribute] +import submod +class A(metaclass=submod.M): + pass +class B(A): + pass +class C(B): + pass +B.x = "" +B.x = 1 +C.x = "" +C.x = 1 +[file submod.py] +class M(type): + x: str +[out] +main:7: error: Incompatible types in assignment (expression has type "int", metaclass "M" defined the type as "str") +main:11: error: Incompatible types in assignment (expression has type "int", metaclass "M" defined the type as "str") diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index b76abd31e3dc4..062502c66b3c7 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -2724,3 +2724,44 @@ TS = TypeVar("TS", bound=str) f: Callable[[Sequence[TI]], None] g: Callable[[Union[Sequence[TI], Sequence[TS]]], None] f = g + +[case testFactoryWithInheritance] +from typing import Type +class A: + x = "foo" +class B(A): + pass +def f() -> Type[B]: + return B +def g() -> B: + return B() + +f().x = "bar" +f().x = 0 # E: Incompatible types in assignment (expression has type "int", base class "A" defined the type as "str") +f()().x = "baz" +f()().x = 1 # E: Incompatible types in assignment (expression has type "int", base class "A" defined the type as "str") +g().x = "" +g().x = 2 # E: Incompatible types in assignment (expression has type "int", base class "A" defined the type as "str") + +[case testMultiLayerCallable] +class A: + y = 0 # Type: int +class B(A): + def __init__(self, z: int) -> None: + self.z = z +def foo(x: int) -> B: + return B(x) +def bar(x: int) -> B: + return foo(x) + +a = bar(1) +a.z = 2 +a.z = "" # E: Incompatible types in assignment (expression has type "str", variable has type "int") +a.y = 2 +a.y = "" # E: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int") + +b = foo(3) +a.z = 4 +a.z = "" # E: Incompatible types in assignment (expression has type "str", variable has type "int") +a.y = 4 +a.y = "" # E: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int") From 162401595846a5403749b08c51aa764ab937aa4a Mon Sep 17 00:00:00 2001 From: Jingchen Ye <11172084+97littleleaf11@users.noreply.github.com> Date: Sat, 13 May 2023 04:50:07 +0800 Subject: [PATCH 4/6] Fix test --- test-data/unit/check-classes.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 271c367fd6027..9f7752d00aba8 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -7890,5 +7890,5 @@ C.x = 1 class M(type): x: str [out] -main:7: error: Incompatible types in assignment (expression has type "int", metaclass "M" defined the type as "str") +main:9: error: Incompatible types in assignment (expression has type "int", metaclass "M" defined the type as "str") main:11: error: Incompatible types in assignment (expression has type "int", metaclass "M" defined the type as "str") From 6362250b4ceb0dfeba0dd5f36c1b315cc1692209 Mon Sep 17 00:00:00 2001 From: Jingchen Ye <11172084+97littleleaf11@users.noreply.github.com> Date: Sat, 13 May 2023 05:00:59 +0800 Subject: [PATCH 5/6] Several modification according to previous review --- mypy/checker.py | 101 +++++++++++++++++++++++++++--------------------- 1 file changed, 58 insertions(+), 43 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index aeb77a09a5e12..ee1f77ee909a9 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -4005,12 +4005,12 @@ def check_member_assignment( # Descriptors don't participate in class-attribute access if isinstance(instance_type, CallableType) and instance_type.is_type_obj(): rvalue_type = self.check_parent_member_assignment( - instance_type.ret_type, attribute_type, lvalue, rvalue + instance_type.ret_type, attribute_type, lvalue, rvalue, context=context ) return rvalue_type, attribute_type, True elif isinstance(instance_type, TypeType): rvalue_type = self.check_parent_member_assignment( - instance_type.item, attribute_type, lvalue, rvalue + instance_type.item, attribute_type, lvalue, rvalue, context=context ) return rvalue_type, attribute_type, True @@ -4036,7 +4036,7 @@ def check_member_assignment( # (which allow you to override the descriptor with any value), but preserves # the type of accessing the attribute (even after the override). rvalue_type = self.check_parent_member_assignment( - instance_type, get_type, lvalue, rvalue + instance_type, get_type, lvalue, rvalue, context=context ) return rvalue_type, get_type, True @@ -4114,51 +4114,66 @@ def check_member_assignment( return rvalue_type if infer else set_type, get_type, infer def check_parent_member_assignment( - self, lvalue_base_type: Type, lvalue_type: Type, lvalue: MemberExpr, rvalue: Expression + self, + lvalue_base_type: Type, + lvalue_type: Type, + lvalue: MemberExpr, + rvalue: Expression, + context: Context, ) -> Type: + """Exactly the same as check_simple_assignment(). + + It adds better error message to indicate the class where the attribute originally defined if possible.""" + lvalue_base_type = get_proper_type(lvalue_base_type) if ( - isinstance(lvalue_base_type, Instance) - and lvalue.name not in lvalue_base_type.type.names + not isinstance(lvalue_base_type, Instance) + or lvalue.name in lvalue_base_type.type.names ): - lvalue_warn = message_registry.INCOMPATIBLE_TYPES_BASECLASS_MISMATCH - metaclass = lvalue_base_type.type.metaclass_type - if metaclass and metaclass.type.has_readable_member(lvalue.name): - if lvalue.name in metaclass.type.names: - meta_attribute = metaclass.type.names[lvalue.name].node - if not (isinstance(meta_attribute, Var) and meta_attribute.info.is_generic()): - return self.check_simple_assignment( - lvalue_type, - rvalue, - context=rvalue, - msg=message_registry.INCOMPATIBLE_TYPES_IN_ASSIGNMENT, - lvalue_name=message_registry.INCOMPATIBLE_TYPES_METACLASS_MISMATCH.format( - metaclass.type.defn.name - ), - ) - else: - # attribute is defined further up in the metaclass MRO - lvalue_warn = 'base class "{{}}" of {}'.format( - message_registry.INCOMPATIBLE_TYPES_METACLASS_MISMATCH.format( + return self.check_simple_assignment(lvalue_type, rvalue, context=context) + + lvalue_warn = message_registry.INCOMPATIBLE_TYPES_BASECLASS_MISMATCH + + metaclass = lvalue_base_type.type.metaclass_type + if metaclass and metaclass.type.has_readable_member(lvalue.name): + if lvalue.name in metaclass.type.names: + meta_attribute = metaclass.type.names[lvalue.name].node + if not (isinstance(meta_attribute, Var) and meta_attribute.info.is_generic()): + return self.check_simple_assignment( + lvalue_type, + rvalue, + context=context, + msg=message_registry.INCOMPATIBLE_TYPES_IN_ASSIGNMENT, + lvalue_name=message_registry.INCOMPATIBLE_TYPES_METACLASS_MISMATCH.format( metaclass.type.defn.name - ) + ), ) - lvalue_base_type = metaclass - if lvalue_base_type.type.has_readable_member(lvalue.name): - for base in lvalue_base_type.type.bases: - if lvalue.name in base.type.names: - parent_context = base.type.names[lvalue.name].node - if isinstance(parent_context, Var) and parent_context.info.is_generic(): - # Attribute may have been defined in this class but its type is - # probably defined elsewhere, possibly in the instance itself. - continue - return self.check_simple_assignment( - lvalue_type, - rvalue, - context=rvalue, - msg=message_registry.INCOMPATIBLE_TYPES_IN_ASSIGNMENT, - lvalue_name=lvalue_warn.format(base.type.defn.name), - ) - return self.check_simple_assignment(lvalue_type, rvalue, context=rvalue) + else: + # attribute is defined further up in the metaclass MRO + # better error message for later check + lvalue_warn = 'base class "{{}}" of {}'.format( + message_registry.INCOMPATIBLE_TYPES_METACLASS_MISMATCH.format( + metaclass.type.defn.name + ) + ) + lvalue_base_type = metaclass + + if lvalue_base_type.type.has_readable_member(lvalue.name): + for base in lvalue_base_type.type.bases: + if lvalue.name in base.type.names: + parent_context = base.type.names[lvalue.name].node + if isinstance(parent_context, Var) and parent_context.info.is_generic(): + # Attribute may have been defined in this class but its type is + # probably defined elsewhere, possibly in the instance itself. + continue + return self.check_simple_assignment( + lvalue_type, + rvalue, + context=context, + msg=message_registry.INCOMPATIBLE_TYPES_IN_ASSIGNMENT, + lvalue_name=lvalue_warn.format(base.type.defn.name), + ) + + return self.check_simple_assignment(lvalue_type, rvalue, context=context) def check_indexed_assignment( self, lvalue: IndexExpr, rvalue: Expression, context: Context From 4bb931ed0315c390f5912fa5ac64396a10a3a93b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 12 May 2023 21:03:22 +0000 Subject: [PATCH 6/6] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/checker.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index ad645be2435e1..b8d22b639b474 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -4137,7 +4137,8 @@ def check_parent_member_assignment( ) -> Type: """Exactly the same as check_simple_assignment(). - It adds better error message to indicate the class where the attribute originally defined if possible.""" + It adds better error message to indicate the class where the attribute originally defined if possible. + """ lvalue_base_type = get_proper_type(lvalue_base_type) if ( not isinstance(lvalue_base_type, Instance)