Skip to content

Commit 60d1b37

Browse files
Fix compatibility checks for conditional function definitions using decorators (#18020)
Fixes #17211, resolves this `# TODO`: https://github.com/python/mypy/blob/e106dd7a0653c24d67597adf3ae6939d7ff9a376/test-data/unit/check-functions.test#L1486-L1494 ### Before ```python from typing import Callable def dec(f: object) -> Callable[[int], None]: raise NotImplementedError if int(): def f(x: str) -> None: pass else: @dec def f() -> None: pass # uh oh! passes without error ``` ### After ```python from typing import Callable def dec(f: object) -> Callable[[int], None]: raise NotImplementedError if int(): def f(x: str) -> None: pass else: @dec def f() -> None: pass # E: All conditional function variants must have identical signatures \ # N: Original: \ # N: def f(x: str) -> None \ # N: Redefinition: \ # N: def f(int, /) -> None ```
1 parent e106dd7 commit 60d1b37

File tree

4 files changed

+60
-49
lines changed

4 files changed

+60
-49
lines changed

mypy/checker.py

Lines changed: 48 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1072,46 +1072,7 @@ def _visit_func_def(self, defn: FuncDef) -> None:
10721072
if defn.original_def:
10731073
# Override previous definition.
10741074
new_type = self.function_type(defn)
1075-
if isinstance(defn.original_def, FuncDef):
1076-
# Function definition overrides function definition.
1077-
old_type = self.function_type(defn.original_def)
1078-
if not is_same_type(new_type, old_type):
1079-
self.msg.incompatible_conditional_function_def(defn, old_type, new_type)
1080-
else:
1081-
# Function definition overrides a variable initialized via assignment or a
1082-
# decorated function.
1083-
orig_type = defn.original_def.type
1084-
if orig_type is None:
1085-
# If other branch is unreachable, we don't type check it and so we might
1086-
# not have a type for the original definition
1087-
return
1088-
if isinstance(orig_type, PartialType):
1089-
if orig_type.type is None:
1090-
# Ah this is a partial type. Give it the type of the function.
1091-
orig_def = defn.original_def
1092-
if isinstance(orig_def, Decorator):
1093-
var = orig_def.var
1094-
else:
1095-
var = orig_def
1096-
partial_types = self.find_partial_types(var)
1097-
if partial_types is not None:
1098-
var.type = new_type
1099-
del partial_types[var]
1100-
else:
1101-
# Trying to redefine something like partial empty list as function.
1102-
self.fail(message_registry.INCOMPATIBLE_REDEFINITION, defn)
1103-
else:
1104-
name_expr = NameExpr(defn.name)
1105-
name_expr.node = defn.original_def
1106-
self.binder.assign_type(name_expr, new_type, orig_type)
1107-
self.check_subtype(
1108-
new_type,
1109-
orig_type,
1110-
defn,
1111-
message_registry.INCOMPATIBLE_REDEFINITION,
1112-
"redefinition with type",
1113-
"original type",
1114-
)
1075+
self.check_func_def_override(defn, new_type)
11151076

11161077
def check_func_item(
11171078
self,
@@ -1147,6 +1108,49 @@ def check_func_item(
11471108
if dataclasses_plugin.is_processed_dataclass(defn.info):
11481109
dataclasses_plugin.check_post_init(self, defn, defn.info)
11491110

1111+
def check_func_def_override(self, defn: FuncDef, new_type: FunctionLike) -> None:
1112+
assert defn.original_def is not None
1113+
if isinstance(defn.original_def, FuncDef):
1114+
# Function definition overrides function definition.
1115+
old_type = self.function_type(defn.original_def)
1116+
if not is_same_type(new_type, old_type):
1117+
self.msg.incompatible_conditional_function_def(defn, old_type, new_type)
1118+
else:
1119+
# Function definition overrides a variable initialized via assignment or a
1120+
# decorated function.
1121+
orig_type = defn.original_def.type
1122+
if orig_type is None:
1123+
# If other branch is unreachable, we don't type check it and so we might
1124+
# not have a type for the original definition
1125+
return
1126+
if isinstance(orig_type, PartialType):
1127+
if orig_type.type is None:
1128+
# Ah this is a partial type. Give it the type of the function.
1129+
orig_def = defn.original_def
1130+
if isinstance(orig_def, Decorator):
1131+
var = orig_def.var
1132+
else:
1133+
var = orig_def
1134+
partial_types = self.find_partial_types(var)
1135+
if partial_types is not None:
1136+
var.type = new_type
1137+
del partial_types[var]
1138+
else:
1139+
# Trying to redefine something like partial empty list as function.
1140+
self.fail(message_registry.INCOMPATIBLE_REDEFINITION, defn)
1141+
else:
1142+
name_expr = NameExpr(defn.name)
1143+
name_expr.node = defn.original_def
1144+
self.binder.assign_type(name_expr, new_type, orig_type)
1145+
self.check_subtype(
1146+
new_type,
1147+
orig_type,
1148+
defn,
1149+
message_registry.INCOMPATIBLE_REDEFINITION,
1150+
"redefinition with type",
1151+
"original type",
1152+
)
1153+
11501154
@contextmanager
11511155
def enter_attribute_inference_context(self) -> Iterator[None]:
11521156
old_types = self.inferred_attribute_types
@@ -5120,6 +5124,10 @@ def visit_decorator_inner(self, e: Decorator, allow_empty: bool = False) -> None
51205124
if e.type and not isinstance(get_proper_type(e.type), (FunctionLike, AnyType)):
51215125
self.fail(message_registry.BAD_CONSTRUCTOR_TYPE, e)
51225126

5127+
if e.func.original_def and isinstance(sig, FunctionLike):
5128+
# Function definition overrides function definition.
5129+
self.check_func_def_override(e.func, sig)
5130+
51235131
def check_for_untyped_decorator(
51245132
self, func: FuncDef, dec_type: Type, dec_expr: Expression
51255133
) -> None:

test-data/unit/check-functions.test

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1474,7 +1474,7 @@ def dec(f) -> Callable[[int], None]: pass
14741474

14751475
x = int()
14761476
if x:
1477-
def f(x: int) -> None: pass
1477+
def f(x: int, /) -> None: pass
14781478
else:
14791479
@dec
14801480
def f(): pass
@@ -1489,9 +1489,12 @@ x = int()
14891489
if x:
14901490
def f(x: str) -> None: pass
14911491
else:
1492-
# TODO: Complain about incompatible redefinition
14931492
@dec
1494-
def f(): pass
1493+
def f(): pass # E: All conditional function variants must have identical signatures \
1494+
# N: Original: \
1495+
# N: def f(x: str) -> None \
1496+
# N: Redefinition: \
1497+
# N: def f(int, /) -> None
14951498

14961499
[case testConditionalFunctionDefinitionUnreachable]
14971500
def bar() -> None:
@@ -1599,7 +1602,7 @@ else:
15991602
def f():
16001603
yield
16011604
[file m.py]
1602-
def f(): pass
1605+
def f() -> None: pass
16031606

16041607
[case testDefineConditionallyAsImportedAndDecoratedWithInference]
16051608
if int():

test-data/unit/check-newsemanal.test

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1908,9 +1908,9 @@ else:
19081908
@dec
19091909
def f(x: int) -> None:
19101910
1() # E: "int" not callable
1911-
reveal_type(f) # N: Revealed type is "def (x: builtins.str)"
1911+
reveal_type(f) # N: Revealed type is "def (builtins.str)"
19121912
[file m.py]
1913-
def f(x: str) -> None: pass
1913+
def f(x: str, /) -> None: pass
19141914

19151915
[case testNewAnalyzerConditionallyDefineFuncOverVar]
19161916
from typing import Callable

test-data/unit/check-overloading.test

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6463,7 +6463,7 @@ class D: ...
64636463
def f1(g: A) -> A: ...
64646464
if True:
64656465
@overload # E: Single overload definition, multiple required
6466-
def f1(g: B) -> B: ...
6466+
def f1(g: B) -> B: ... # E: Incompatible redefinition (redefinition with type "Callable[[B], B]", original type "Callable[[A], A]")
64676467
if maybe_true: # E: Condition can't be inferred, unable to merge overloads \
64686468
# E: Name "maybe_true" is not defined
64696469
@overload
@@ -6480,14 +6480,14 @@ if True:
64806480
def f2(g: B) -> B: ...
64816481
elif maybe_true: # E: Name "maybe_true" is not defined
64826482
@overload # E: Single overload definition, multiple required
6483-
def f2(g: C) -> C: ...
6483+
def f2(g: C) -> C: ... # E: Incompatible redefinition (redefinition with type "Callable[[C], C]", original type "Callable[[A], A]")
64846484
def f2(g): ... # E: Name "f2" already defined on line 21
64856485

64866486
@overload # E: Single overload definition, multiple required
64876487
def f3(g: A) -> A: ...
64886488
if True:
64896489
@overload # E: Single overload definition, multiple required
6490-
def f3(g: B) -> B: ...
6490+
def f3(g: B) -> B: ... # E: Incompatible redefinition (redefinition with type "Callable[[B], B]", original type "Callable[[A], A]")
64916491
if True:
64926492
pass # Some other node
64936493
@overload # E: Name "f3" already defined on line 32 \

0 commit comments

Comments
 (0)