Skip to content

Commit 18815c7

Browse files
committed
Handle allow_incompatible_override consistenly instead
1 parent bd953b4 commit 18815c7

File tree

3 files changed

+31
-24
lines changed

3 files changed

+31
-24
lines changed

mypy/checker.py

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2216,6 +2216,9 @@ def check_method_override_for_base_with_name(
22162216
# Will always fail to typecheck below, since we know the node is a method
22172217
original_type = NoneType()
22182218

2219+
if isinstance(original_node, Var) and original_node.allow_incompatible_override:
2220+
return False
2221+
22192222
always_allow_covariant = False
22202223
if is_settable_property(defn) and (
22212224
is_settable_property(original_node) or isinstance(original_node, Var)
@@ -2239,14 +2242,6 @@ def check_method_override_for_base_with_name(
22392242
typ = get_proper_type(typ)
22402243
original_type = get_proper_type(original_type)
22412244

2242-
if name == "__hash__" and isinstance(original_type, NoneType):
2243-
# Allow defining `__hash__` even if parent class was explicitly made unhashable
2244-
if base.fullname == "builtins.object":
2245-
# This is only for test stubs to avoid adding object.__hash__
2246-
# to all of them.
2247-
return True
2248-
return self.check_method_override_for_base_with_name(defn, name, base.mro[-1])
2249-
22502245
if (
22512246
is_property(defn)
22522247
and isinstance(original_node, Var)
@@ -3448,13 +3443,6 @@ def check_compatibility_all_supers(self, lvalue: RefExpr, rvalue: Expression) ->
34483443
return
34493444

34503445
for base in lvalue_node.info.mro[1:]:
3451-
# The type of "__slots__" and some other attributes usually doesn't need to
3452-
# be compatible with a base class. We'll still check the type of "__slots__"
3453-
# against "object" as an exception.
3454-
if lvalue_node.allow_incompatible_override and not (
3455-
lvalue_node.name == "__slots__" and base.fullname == "builtins.object"
3456-
):
3457-
continue
34583446
if (
34593447
lvalue_node.name == "__hash__"
34603448
and base.fullname == "builtins.object"
@@ -3468,6 +3456,17 @@ def check_compatibility_all_supers(self, lvalue: RefExpr, rvalue: Expression) ->
34683456
continue
34693457

34703458
base_type, base_node = self.node_type_from_base(lvalue_node.name, base, lvalue)
3459+
# The type of "__slots__" and some other attributes usually doesn't need to
3460+
# be compatible with a base class. We'll still check the type of "__slots__"
3461+
# against "object" as an exception.
3462+
if (
3463+
isinstance(base_node, Var)
3464+
and base_node.allow_incompatible_override
3465+
and not (
3466+
lvalue_node.name == "__slots__" and base.fullname == "builtins.object"
3467+
)
3468+
):
3469+
continue
34713470
custom_setter = is_custom_settable_property(base_node)
34723471
if isinstance(base_type, PartialType):
34733472
base_type = None
@@ -3497,7 +3496,7 @@ def check_compatibility_all_supers(self, lvalue: RefExpr, rvalue: Expression) ->
34973496
lvalue, lvalue_type, base_type, base
34983497
)
34993498
return
3500-
if base is last_immediate_base and base_node.name != "__hash__":
3499+
if base is last_immediate_base:
35013500
# At this point, the attribute was found to be compatible with all
35023501
# immediate parents.
35033502
break
@@ -3511,9 +3510,6 @@ def check_compatibility_super(
35113510
base_node: Node,
35123511
always_allow_covariant: bool,
35133512
) -> bool:
3514-
if base_node.name == "__hash__" and isinstance(base_type, NoneType):
3515-
# Allow defining `__hash__` even if parent class was explicitly made unhashable
3516-
return True
35173513
# TODO: check __set__() type override for custom descriptors.
35183514
# TODO: for descriptors check also class object access override.
35193515
ok = self.check_subtype(

mypy/semanal.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3257,6 +3257,22 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None:
32573257
self.process__all__(s)
32583258
self.process__deletable__(s)
32593259
self.process__slots__(s)
3260+
self.process__hash__(s)
3261+
3262+
def process__hash__(self, s: AssignmentStmt) -> None:
3263+
# Allow overriding `__hash__ = None` in subclasses.
3264+
if (
3265+
isinstance(self.type, TypeInfo)
3266+
and len(s.lvalues) == 1
3267+
and isinstance(s.lvalues[0], NameExpr)
3268+
and s.lvalues[0].name == "__hash__"
3269+
and s.lvalues[0].kind == MDEF
3270+
and isinstance(s.rvalue, NameExpr)
3271+
and s.rvalue.name == "None"
3272+
):
3273+
var = s.lvalues[0].node
3274+
if isinstance(var, Var):
3275+
var.allow_incompatible_override = True
32603276

32613277
def analyze_identity_global_assignment(self, s: AssignmentStmt) -> bool:
32623278
"""Special case 'X = X' in global scope.

test-data/unit/check-classes.test

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -163,10 +163,6 @@ class C(A):
163163

164164
class D(A):
165165
def __hash__(self, x: int) -> str: # E: Signature of "__hash__" incompatible with supertype "object" \
166-
# N: Superclass: \
167-
# N: def __hash__(self) -> int \
168-
# N: Subclass: \
169-
# N: def __hash__(self, x: int) -> str \
170166
# N: Superclass: \
171167
# N: def __hash__(self) -> int \
172168
# N: Subclass: \
@@ -178,7 +174,6 @@ def bad_hash(x: E, y: str) -> str:
178174

179175
class E(A):
180176
__hash__ = bad_hash # E: Incompatible types in assignment (expression has type "Callable[[str], str]", base class "object" defined the type as "Callable[[], int]")
181-
182177
[builtins fixtures/primitives.pyi]
183178

184179
[case testHashNoneBadOverride]

0 commit comments

Comments
 (0)