Skip to content

Commit 324111d

Browse files
ilevkivskyiJukkaL
authored andcommitted
Move analysis of function signature to the first phase (#5461)
Fixes #5454. This fixes a few similar overload crash scenarios. The idea is quite simple: previously during first phase of function analysis we only set the full name and added it to locals, while during the second phase we analysed the signature and the function body. This however doesn't work with overloads, since we want to collect all item types into a single `Overloaded(...)`. What I propose is to just move analysis of signature and the initialisers to the first phase. IMO this is more logical (we just want to postpone the bodies), and is more close to runtime semantics (especially the initialisers that are evaluated immediately during function definition).
1 parent 3e435f2 commit 324111d

File tree

2 files changed

+91
-15
lines changed

2 files changed

+91
-15
lines changed

mypy/semanal.py

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,26 @@ def _visit_func_def(self, defn: FuncDef) -> None:
416416
if not self.set_original_def(symbol.node, defn):
417417
# Report error.
418418
self.check_no_global(defn.name(), defn, True)
419+
420+
# Analyze function signature and initializers in the first phase
421+
# (at least this mirrors what happens at runtime).
422+
with self.tvar_scope_frame(self.tvar_scope.method_frame()):
423+
if defn.type:
424+
self.check_classvar_in_signature(defn.type)
425+
assert isinstance(defn.type, CallableType)
426+
# Signature must be analyzed in the surrounding scope so that
427+
# class-level imported names and type variables are in scope.
428+
analyzer = self.type_analyzer()
429+
defn.type = analyzer.visit_callable_type(defn.type, nested=False)
430+
self.add_type_alias_deps(analyzer.aliases_used)
431+
self.check_function_signature(defn)
432+
if isinstance(defn, FuncDef):
433+
assert isinstance(defn.type, CallableType)
434+
defn.type = set_callable_name(defn.type, defn)
435+
for arg in defn.arguments:
436+
if arg.initializer:
437+
arg.initializer.accept(self)
438+
419439
if phase_info == FUNCTION_FIRST_PHASE_POSTPONE_SECOND:
420440
# Postpone this function (for the second phase).
421441
self.postponed_functions_stack[-1].append(defn)
@@ -646,21 +666,6 @@ def analyze_property_with_multi_part_definition(self, defn: OverloadedFuncDef) -
646666
def analyze_function(self, defn: FuncItem) -> None:
647667
is_method = self.is_class_scope()
648668
with self.tvar_scope_frame(self.tvar_scope.method_frame()):
649-
if defn.type:
650-
self.check_classvar_in_signature(defn.type)
651-
assert isinstance(defn.type, CallableType)
652-
# Signature must be analyzed in the surrounding scope so that
653-
# class-level imported names and type variables are in scope.
654-
analyzer = self.type_analyzer()
655-
defn.type = analyzer.visit_callable_type(defn.type, nested=False)
656-
self.add_type_alias_deps(analyzer.aliases_used)
657-
self.check_function_signature(defn)
658-
if isinstance(defn, FuncDef):
659-
assert isinstance(defn.type, CallableType)
660-
defn.type = set_callable_name(defn.type, defn)
661-
for arg in defn.arguments:
662-
if arg.initializer:
663-
arg.initializer.accept(self)
664669
# Bind the type variables again to visit the body.
665670
if defn.type:
666671
a = self.type_analyzer()

test-data/unit/check-overloading.test

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4186,3 +4186,74 @@ def g(x: str) -> int: ...
41864186
[builtins fixtures/list.pyi]
41874187
[typing fixtures/typing-full.pyi]
41884188
[out]
4189+
4190+
[case testNestedOverloadsNoCrash]
4191+
from typing import overload
4192+
4193+
def f() -> None:
4194+
@overload
4195+
def g(x: str) -> str: ...
4196+
@overload
4197+
def g(x: int) -> int: ...
4198+
def g(x):
4199+
pass
4200+
g(str())
4201+
[out]
4202+
4203+
[case testNestedOverloadsTypeVar]
4204+
from typing import overload, TypeVar
4205+
4206+
T = TypeVar('T')
4207+
4208+
def f() -> None:
4209+
@overload
4210+
def g(x: str) -> str: ...
4211+
@overload
4212+
def g(x: T, y: int) -> T: ...
4213+
def g(x):
4214+
pass
4215+
4216+
g(str(), str()) # E: No overload variant of "g" matches argument types "str", "str" \
4217+
# N: Possible overload variant: \
4218+
# N: def [T] g(x: T, y: int) -> T \
4219+
# N: <1 more non-matching overload not shown>
4220+
reveal_type(g(str(), int())) # E: Revealed type is 'builtins.str*'
4221+
[out]
4222+
4223+
[case testNestedOverloadsTypeVarOverlap]
4224+
from typing import overload, TypeVar
4225+
4226+
T = TypeVar('T')
4227+
4228+
def f() -> None:
4229+
@overload
4230+
def g(x: str) -> int: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types
4231+
@overload
4232+
def g(x: T) -> T: ...
4233+
def g(x):
4234+
pass
4235+
[out]
4236+
4237+
[case testNestedOverloadsMutuallyRecursive]
4238+
from typing import overload, TypeVar, Dict, Any
4239+
4240+
class C: ...
4241+
T = TypeVar('T')
4242+
4243+
def f() -> None:
4244+
@overload
4245+
def g() -> None: ...
4246+
@overload
4247+
def g(x: T) -> Dict[int, T]: ...
4248+
def g(*args, **kwargs) -> Any:
4249+
reveal_type(h(C())) # E: Revealed type is 'builtins.dict[builtins.str, __main__.C*]'
4250+
4251+
@overload
4252+
def h() -> None: ...
4253+
@overload
4254+
def h(x: T) -> Dict[str, T]: ...
4255+
def h(*args, **kwargs) -> Any:
4256+
reveal_type(g(C())) # E: Revealed type is 'builtins.dict[builtins.int, __main__.C*]'
4257+
4258+
[builtins fixtures/dict.pyi]
4259+
[out]

0 commit comments

Comments
 (0)