Skip to content

Commit b8488e1

Browse files
committed
Unsoundly narrow away from None with custom eq
In #20754 we see some primer hits where this unsoundness is beneficial
1 parent 525c6d1 commit b8488e1

3 files changed

Lines changed: 27 additions & 1 deletion

File tree

mypy/checker.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6746,7 +6746,14 @@ def narrow_type_by_identity_equality(
67466746
if i == j:
67476747
continue
67486748
# If we compare to a target with custom __eq__, we cannot narrow at all
6749-
or_if_maps.append({})
6749+
if is_overlapping_none(expr_type) and not is_overlapping_none(
6750+
operand_types[j]
6751+
):
6752+
# Narrow away from None. This is unsound, we're hoping that no one
6753+
# has a custom __eq__ that returns True for None.
6754+
or_if_maps.append({operands[i]: remove_optional(expr_type)})
6755+
else:
6756+
or_if_maps.append({operands[i]: expr_type})
67506757
continue
67516758
target_type = operand_types[j]
67526759
if should_coerce_literals:

mypy/types_utils.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
TypeAliasType,
2727
TypeType,
2828
TypeVarType,
29+
UninhabitedType,
2930
UnionType,
3031
UnpackType,
3132
flatten_nested_unions,
@@ -134,6 +135,8 @@ def remove_optional(typ: Type) -> Type:
134135
return UnionType.make_union(
135136
[t for t in typ.items if not isinstance(get_proper_type(t), NoneType)]
136137
)
138+
elif isinstance(typ, NoneType):
139+
return UninhabitedType()
137140
else:
138141
return typ
139142

test-data/unit/check-narrowing.test

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1008,6 +1008,22 @@ def f(x: Custom | None, y: int | None):
10081008
[case testNarrowingCustomEqualityUnion4]
10091009
# flags: --strict-equality --warn-unreachable
10101010
from __future__ import annotations
1011+
1012+
class Custom:
1013+
def __eq__(self, other: object) -> bool:
1014+
return True
1015+
1016+
def f(x: Custom | None, y: Custom):
1017+
if x == y:
1018+
# We unsoundly special case None and narrow x to Custom here
1019+
reveal_type(x) # N: Revealed type is "__main__.Custom"
1020+
else:
1021+
reveal_type(x) # N: Revealed type is "__main__.Custom | None"
1022+
[builtins fixtures/primitives.pyi]
1023+
1024+
[case testNarrowingCustomEqualityUnion5]
1025+
# flags: --strict-equality --warn-unreachable
1026+
from __future__ import annotations
10111027
from typing import Any
10121028

10131029
class Custom1:

0 commit comments

Comments
 (0)