diff --git a/mypy/checker.py b/mypy/checker.py index 2f99b9b4fece..bb47f44e68fa 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2267,13 +2267,6 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type self.check_compatibility_all_supers(lvalue, lvalue_type, rvalue)): # We hit an error on this line; don't check for any others return - elif (is_literal_none(rvalue) and - isinstance(lvalue, NameExpr) and - isinstance(lvalue.node, Var) and - lvalue.node.is_initialized_in_class and - not new_syntax): - # Allow None's to be assigned to class variables with non-Optional types. - rvalue_type = lvalue_type elif (isinstance(lvalue, MemberExpr) and lvalue.kind is None): # Ignore member access to modules instance_type = self.expr_checker.accept(lvalue.expr) @@ -3045,8 +3038,40 @@ def check_lvalue(self, lvalue: Lvalue) -> Tuple[Optional[Type], not isinstance(lvalue, NameExpr) or isinstance(lvalue.node, Var) ): if isinstance(lvalue, NameExpr): - inferred = cast(Var, lvalue.node) - assert isinstance(inferred, Var) + # If this is a class variable without a type annotation and + # there's a base class with the same name then set the type to + # that variable's type. + # + # We need to check lvalue.node.info.__bool__() because it can + # be FakeInfo. + type_set = False + if ( + isinstance(lvalue.node, Var) and + lvalue.node.type is None and + lvalue.node.info and + not (lvalue.name.startswith("__") and lvalue.name.endswith("__")) + ): + + for base in lvalue.node.info.mro[1:]: + base_type, _base_node = self.lvalue_type_from_base(lvalue.node, base) + + if base_type: + # Give up on callables for now. MyPy does not deal with + # them properly. + # See https://github.com/microsoft/pyright/issues/2805 + if isinstance(get_proper_type(base_type), CallableType): + break + lvalue_type = base_type + lvalue.node.type = lvalue_type + self.store_type(lvalue, lvalue_type) + type_set = True + break + + # If the type still isn't set, infer it to be any var. + if not type_set: + inferred = cast(Var, lvalue.node) + assert isinstance(inferred, Var) + else: assert isinstance(lvalue, MemberExpr) self.expr_checker.accept(lvalue.expr) diff --git a/mypyc/test-data/irbuild-classes.test b/mypyc/test-data/irbuild-classes.test index 77943045ffe3..0c7d511eefd9 100644 --- a/mypyc/test-data/irbuild-classes.test +++ b/mypyc/test-data/irbuild-classes.test @@ -1067,7 +1067,7 @@ class B(A): y = LOL z: Optional[str] = None b = True - bogus = None # type: int + bogus = None # type: Optional[int] [out] def A.lol(self): self :: __main__.A @@ -1091,6 +1091,8 @@ def B.__mypyc_defaults_setup(__mypyc_self__): r5 :: bool r6 :: object r7, r8 :: bool + r9 :: object + r10 :: bool L0: __mypyc_self__.x = 20; r0 = is_error r1 = __main__.globals :: static @@ -1101,6 +1103,8 @@ L0: r6 = box(None, 1) __mypyc_self__.z = r6; r7 = is_error __mypyc_self__.b = 1; r8 = is_error + r9 = box(None, 1) + __mypyc_self__.bogus = r9; r10 = is_error return 1 [case testSubclassDictSpecalized] diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index a2bcdade3287..a6b1f55f8edb 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -4339,7 +4339,7 @@ class C(B): x = object() [out] main:4: error: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int") -main:6: error: Incompatible types in assignment (expression has type "object", base class "B" defined the type as "str") +main:6: error: Incompatible types in assignment (expression has type "object", variable has type "str") [case testClassOneErrorPerLine] class A: @@ -7134,3 +7134,27 @@ class B(A): # E: Final class __main__.B has abstract attributes "foo" [case testUndefinedBaseclassInNestedClass] class C: class C1(XX): pass # E: Name "XX" is not defined + +[case testInheritClassVariableType] +# flags: --python-version 3.7 +from __future__ import annotations +class Base: + X: list[int] = [] +class Derived0(Base): + X = [] +class Derived1(Base): + X: list[str] = [] # E: Incompatible types in assignment (expression has type "List[str]", base class "Base" defined the type as "List[int]") +class Derived2(Base): + X = ["a"] # E: List item 0 has incompatible type "str"; expected "int" +[builtins fixtures/tuple.pyi] + +[case testInheritClassVariableTypeIncompatibility] +# flags: --python-version 3.7 +from __future__ import annotations +class A: + x: list[int] +class B(A): + x: list[str] # E: Incompatible types in assignment (expression has type "List[str]", base class "A" defined the type as "List[int]") +class C(B): + x = ["hi"] +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-formatting.test b/test-data/unit/check-formatting.test index 783c31c18770..1cd759867890 100644 --- a/test-data/unit/check-formatting.test +++ b/test-data/unit/check-formatting.test @@ -524,7 +524,7 @@ class D(bytes): [case testFormatCallFormatTypesBytesNotPy2] # flags: --py2 -from typing import Union, TypeVar, NewType, Generic +from typing import Union, TypeVar, NewType, Generic, Optional A = TypeVar('A', str, unicode) B = TypeVar('B', bound=str) @@ -547,7 +547,7 @@ u'{}'.format(x) u'{}'.format(n) class C(Generic[B]): - x = None # type: B + x = None # type: Optional[B] def meth(self): # type: () -> None '{}'.format(self.x) diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index 1a73f9f33274..1284844114e6 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -5205,7 +5205,7 @@ def test() -> None: [out] [out2] -[case testCannotDetermineTypeFromOtherModule] +[case testCanDetermineTypeFromOtherModule] import aa @@ -5233,15 +5233,15 @@ class Base: def foo(self) -> int: ... class Sub(Base): - foo = desc(42) # type: ignore + foo = desc(42) [builtins fixtures/property.pyi] [out] -tmp/a.py:3: error: Cannot determine type of "foo" -tmp/a.py:4: error: Cannot determine type of "foo" +tmp/b.py:12: error: Too many arguments for "desc" +tmp/b.py:12: error: Incompatible types in assignment (expression has type "desc", variable has type "int") [out2] -tmp/a.py:3: error: Cannot determine type of "foo" -tmp/a.py:4: error: Cannot determine type of "foo" +tmp/b.py:12: error: Too many arguments for "desc" +tmp/b.py:12: error: Incompatible types in assignment (expression has type "desc", variable has type "int") [case testRedefinitionClass] import b diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index 4de6e4a76f92..4633f56735f6 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -2575,7 +2575,7 @@ class A: class B(A): x = None -reveal_type(B.x) # N: Revealed type is "None" +reveal_type(B.x) # N: Revealed type is "Union[builtins.str, None]" [case testLocalPartialTypesWithInheritance2] # flags: --local-partial-types --strict-optional @@ -2583,7 +2583,7 @@ class A: x: str class B(A): - x = None # E: Incompatible types in assignment (expression has type "None", base class "A" defined the type as "str") + x = None # E: Incompatible types in assignment (expression has type "None", variable has type "str") [case testLocalPartialTypesWithAnyBaseClass] # flags: --local-partial-types --strict-optional @@ -2611,8 +2611,8 @@ class C(B): x = None # TODO: Inferring None below is unsafe (https://github.com/python/mypy/issues/3208) -reveal_type(B.x) # N: Revealed type is "None" -reveal_type(C.x) # N: Revealed type is "None" +reveal_type(B.x) # N: Revealed type is "Union[builtins.str, None]" +reveal_type(C.x) # N: Revealed type is "Union[builtins.str, None]" [case testLocalPartialTypesWithInheritance3] # flags: --local-partial-types @@ -2628,7 +2628,7 @@ class B(A): x = None x = Y() -reveal_type(B.x) # N: Revealed type is "Union[__main__.Y, None]" +reveal_type(B.x) # N: Revealed type is "Union[__main__.X, None]" [case testLocalPartialTypesBinderSpecialCase] # flags: --local-partial-types @@ -2813,8 +2813,8 @@ class C(A): x = ['12'] reveal_type(A.x) # N: Revealed type is "builtins.list[Any]" -reveal_type(B.x) # N: Revealed type is "builtins.list[builtins.int]" -reveal_type(C.x) # N: Revealed type is "builtins.list[builtins.str]" +reveal_type(B.x) # N: Revealed type is "builtins.list[Any]" +reveal_type(C.x) # N: Revealed type is "builtins.list[Any]" [builtins fixtures/list.pyi] @@ -2920,7 +2920,7 @@ class A: class B(A): x = None - x = 2 # E: Incompatible types in assignment (expression has type "int", base class "A" defined the type as "str") + x = 2 # E: Incompatible types in assignment (expression has type "int", variable has type "str") [case testInheritedAttributeStrictOptional] # flags: --strict-optional @@ -2928,7 +2928,7 @@ class A: x: str class B(A): - x = None # E: Incompatible types in assignment (expression has type "None", base class "A" defined the type as "str") + x = None # E: Incompatible types in assignment (expression has type "None", variable has type "str") x = '' [case testNeedAnnotationForCallable] diff --git a/test-data/unit/check-optional.test b/test-data/unit/check-optional.test index c6d3d1f48649..0bbe76a10929 100644 --- a/test-data/unit/check-optional.test +++ b/test-data/unit/check-optional.test @@ -215,14 +215,14 @@ x() # E: "Dict[str, int]" not callable [case testNoneClassVariable] from typing import Optional class C: - x = None # type: int + x = None # type: Optional[int] def __init__(self) -> None: self.x = 0 [case testNoneClassVariableInInit] from typing import Optional class C: - x = None # type: int + x = 0 # type: int def __init__(self) -> None: self.x = None # E: Incompatible types in assignment (expression has type "None", variable has type "int") [out] @@ -230,7 +230,7 @@ class C: [case testMultipleAssignmentNoneClassVariableInInit] from typing import Optional class C: - x, y = None, None # type: int, str + x, y = 0, "" # type: int, str def __init__(self) -> None: self.x = None # E: Incompatible types in assignment (expression has type "None", variable has type "int") self.y = None # E: Incompatible types in assignment (expression has type "None", variable has type "str") diff --git a/test-data/unit/deps-types.test b/test-data/unit/deps-types.test index d0674dfadceb..0c46d1a15191 100644 --- a/test-data/unit/deps-types.test +++ b/test-data/unit/deps-types.test @@ -279,8 +279,9 @@ def f(arg): # type: (Type[C]) -> None arg.x [file mod.py] +from typing import Optional class M(type): - x = None # type: int + x = None # type: Optional[int] class C: __metaclass__ = M [out] diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index ad67ff19dfd2..575765fd9dbb 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -2962,20 +2962,24 @@ import submod class C: __metaclass__ = submod.M [file submod.py] +from typing import Optional class M(type): - x = None # type: int + x = None # type: Optional[int] [file submod.py.2] +from typing import Optional class M(type): - x = None # type: str + x = None # type: Optional[str] [file submod.py.3] +from typing import Optional class M(type): - y = None # type: str + y = None # type: Optional[str] [file submod.py.4] +from typing import Optional class M(type): - x = None # type: int + x = None # type: Optional[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", variable has type "Optional[str]") == a.py:4: error: "Type[C]" has no attribute "x" == @@ -3686,7 +3690,7 @@ class BaseS: x: str [out] == -b.py:3: error: Incompatible types in assignment (expression has type "int", base class "BaseS" defined the type as "str") +b.py:3: error: Incompatible types in assignment (expression has type "int", variable has type "str") [case testAliasFineGenericMod] import b