Skip to content

Commit 0aa07ea

Browse files
committed
erase NotImplementedType from the return type of normally applied binary methods ("+", "and", etc.).
1 parent 9b584b6 commit 0aa07ea

File tree

4 files changed

+75
-13
lines changed

4 files changed

+75
-13
lines changed

mypy/checker.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4894,10 +4894,19 @@ def infer_context_dependent(
48944894
self.store_types(original_type_map)
48954895
return typ
48964896

4897-
def check_return_stmt(self, s: ReturnStmt) -> None:
4897+
@staticmethod
4898+
def is_notimplemented(t: ProperType) -> Type:
4899+
return (isinstance(t, Instance) and t.type.fullname == "builtins._NotImplementedType")
4900+
4901+
@classmethod
4902+
def erase_notimplemented(cls, t: ProperType) -> Type:
4903+
if cls.is_notimplemented(t):
4904+
return AnyType(TypeOfAny.special_form)
4905+
if isinstance(t, UnionType):
4906+
return UnionType.make_union([i for i in t.items if not cls.is_notimplemented(i)])
4907+
return t
48984908

4899-
def is_notimplemented(t: object) -> bool:
4900-
return isinstance(t, Instance) and t.type.fullname == "builtins._NotImplementedType"
4909+
def check_return_stmt(self, s: ReturnStmt) -> None:
49014910

49024911
defn = self.scope.current_function()
49034912
if defn is not None:
@@ -4979,12 +4988,7 @@ def is_notimplemented(t: object) -> bool:
49794988
else:
49804989
typ_: Type = typ
49814990
if defn.name in BINARY_MAGIC_METHODS or defn.name == "__subclasshook__":
4982-
if is_notimplemented(typ):
4983-
return
4984-
if isinstance(typ, UnionType):
4985-
typ_ = UnionType.make_union(
4986-
[i for i in typ.items if not is_notimplemented(i)]
4987-
)
4991+
typ_ = self.erase_notimplemented(typ)
49884992
self.check_subtype(
49894993
subtype_label="got",
49904994
subtype=typ_,

mypy/checkexpr.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3554,7 +3554,7 @@ def visit_op_expr(self, e: OpExpr) -> Type:
35543554
else:
35553555
assert_never(use_reverse)
35563556
e.method_type = method_type
3557-
return result
3557+
return self.chk.erase_notimplemented(result)
35583558
else:
35593559
raise RuntimeError(f"Unknown operator {e.op}")
35603560

@@ -3705,7 +3705,7 @@ def visit_comparison_expr(self, e: ComparisonExpr) -> Type:
37053705
result = join.join_types(result, sub_result)
37063706

37073707
assert result is not None
3708-
return result
3708+
return self.chk.erase_notimplemented(result)
37093709

37103710
def find_partial_type_ref_fast_path(self, expr: Expression) -> Type | None:
37113711
"""If expression has a partial generic type, return it without additional checks.
@@ -4228,15 +4228,16 @@ def check_op(
42284228
# callable types.
42294229
results_final = make_simplified_union(all_results)
42304230
inferred_final = self.combine_function_signatures(get_proper_types(all_inferred))
4231-
return results_final, inferred_final
4231+
return self.chk.erase_notimplemented(results_final), inferred_final
42324232
else:
4233-
return self.check_method_call_by_name(
4233+
result, inferred = self.check_method_call_by_name(
42344234
method=method,
42354235
base_type=base_type,
42364236
args=[arg],
42374237
arg_kinds=[ARG_POS],
42384238
context=context,
42394239
)
4240+
return self.chk.erase_notimplemented(result), inferred
42404241

42414242
def check_boolean_op(self, e: OpExpr) -> Type:
42424243
"""Type check a boolean operation ('and' or 'or')."""

test-data/unit/check-overloading.test

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6887,3 +6887,59 @@ class A:
68876887
def i(self) -> Union[bool, NotImplementedType]: return NotImplemented
68886888
def j(self) -> Union[bool, NotImplementedType]: return True
68896889
[builtins fixtures/notimplemented.pyi]
6890+
6891+
[case testNotImplementedReturnedFromBinaryMagicMethod]
6892+
# flags: --warn-unreachable
6893+
from typing import Union
6894+
6895+
class A:
6896+
def __add__(self, x: A) -> Union[int, NotImplementedType]: ...
6897+
def __sub__(self, x: A) -> NotImplementedType: ...
6898+
def __imul__(self, x: A) -> Union[A, NotImplementedType]: ...
6899+
def __itruediv__(self, x: A) -> Union[A, NotImplementedType]: ...
6900+
def __ifloordiv__(self, x: A) -> Union[int, NotImplementedType]: ...
6901+
def __eq__(self, x: object) -> Union[bool, NotImplementedType]: ...
6902+
def __le__(self, x: int) -> Union[bool, NotImplementedType]: ...
6903+
def __lt__(self, x: int) -> NotImplementedType: ...
6904+
def __and__(self, x: object) -> NotImplementedType: ...
6905+
class B(A):
6906+
def __radd__(self, x: A) -> Union[int, NotImplementedType]: ...
6907+
def __rsub__(self, x: A) -> NotImplementedType: ...
6908+
def __itruediv__(self, x: A) -> Union[A, NotImplementedType]: ...
6909+
def __ror__(self, x: object) -> NotImplementedType: ...
6910+
6911+
a: A
6912+
b: B
6913+
6914+
reveal_type(a.__add__(a)) # N: Revealed type is "Union[builtins.int, builtins._NotImplementedType]"
6915+
reveal_type(a.__sub__(a)) # N: Revealed type is "builtins._NotImplementedType"
6916+
reveal_type(a.__imul__(a)) # N: Revealed type is "Union[__main__.A, builtins._NotImplementedType]"
6917+
reveal_type(a.__eq__(a)) # N: Revealed type is "Union[builtins.bool, builtins._NotImplementedType]"
6918+
reveal_type(a.__le__(1)) # N: Revealed type is "Union[builtins.bool, builtins._NotImplementedType]"
6919+
6920+
reveal_type(a + a) # N: Revealed type is "builtins.int"
6921+
reveal_type(a - a) # N: Revealed type is "Any"
6922+
reveal_type(a + b) # N: Revealed type is "builtins.int"
6923+
reveal_type(a - b) # N: Revealed type is "Any"
6924+
def f1(a: A) -> None:
6925+
a += a # E: Incompatible types in assignment (expression has type "int", variable has type "A")
6926+
def f2(a: A) -> None:
6927+
a -= a
6928+
reveal_type(a) # N: Revealed type is "__main__.A"
6929+
def f3(a: A) -> None:
6930+
a *= a
6931+
reveal_type(a) # N: Revealed type is "__main__.A"
6932+
def f4(a: A) -> None:
6933+
a /= a
6934+
reveal_type(a) # N: Revealed type is "__main__.A"
6935+
def f5(a: A) -> None:
6936+
a //= a # E: Result type of // incompatible in assignment
6937+
reveal_type(a == a) # N: Revealed type is "builtins.bool"
6938+
reveal_type(a == 1) # N: Revealed type is "builtins.bool"
6939+
reveal_type(a <= 1) # N: Revealed type is "builtins.bool"
6940+
reveal_type(a < 1) # N: Revealed type is "Any"
6941+
reveal_type(a and int()) # N: Revealed type is "Union[__main__.A, builtins.int]"
6942+
reveal_type(int() or a) # N: Revealed type is "Union[builtins.int, __main__.A]"
6943+
6944+
[builtins fixtures/notimplemented.pyi]
6945+

test-data/unit/fixtures/notimplemented.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class str: pass
1212
class tuple: pass
1313
class dict: pass
1414
class classmethod: pass
15+
class ellipsis: pass
1516

1617
class _NotImplementedType:
1718
__call__: NotImplemented # type: ignore

0 commit comments

Comments
 (0)