Skip to content

Commit 8f11c23

Browse files
committed
Merge branch 'nested-function-none-init'
Partially addresses #649.
2 parents 06fc68b + e8b7167 commit 8f11c23

File tree

2 files changed

+50
-1
lines changed

2 files changed

+50
-1
lines changed

mypy/semanal.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,12 @@ def visit_func_def(self, defn: FuncDef) -> None:
221221

222222
defn.is_conditional = self.block_depth[-1] > 0
223223

224+
# TODO(jukka): Figure out how to share the various cases. It doesn't
225+
# make sense to have (almost) duplicate code (here and elsewhere) for
226+
# 3 cases: module-level, class-level and local names. Maybe implement
227+
# a common stack of namespaces. As the 3 kinds of namespaces have
228+
# different semantics, this wouldn't always work, but it might still
229+
# be a win.
224230
if self.is_class_scope():
225231
# Method definition
226232
defn.info = self.type
@@ -237,7 +243,15 @@ def visit_func_def(self, defn: FuncDef) -> None:
237243
elif self.is_func_scope():
238244
# Nested function
239245
if not defn.is_decorated and not defn.is_overload:
240-
self.add_local(defn, defn)
246+
if defn.name() in self.locals[-1]:
247+
# Redefinition. Conditional redefinition is okay.
248+
n = self.locals[-1][defn.name()].node
249+
if self.is_conditional_func(n, defn):
250+
defn.original_def = cast(FuncDef, n)
251+
else:
252+
self.name_already_defined(defn.name(), defn)
253+
else:
254+
self.add_local(defn, defn)
241255
else:
242256
# Top-level function
243257
if not defn.is_decorated and not defn.is_overload:
@@ -2026,11 +2040,14 @@ def enter(self) -> None:
20262040
self.locals.append(SymbolTable())
20272041
self.global_decls.append(set())
20282042
self.nonlocal_decls.append(set())
2043+
# -1 since entering block will increment this to 0.
2044+
self.block_depth.append(-1)
20292045

20302046
def leave(self) -> None:
20312047
self.locals.pop()
20322048
self.global_decls.pop()
20332049
self.nonlocal_decls.pop()
2050+
self.block_depth.pop()
20342051

20352052
def is_func_scope(self) -> bool:
20362053
return self.locals[-1] is not None

mypy/test/data/check-functions.test

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -866,6 +866,27 @@ main:9: error: Unsupported left operand type for + ("int")
866866
main: note: At top level:
867867
main:12: error: Argument 1 to "f" has incompatible type "str"; expected "int"
868868

869+
[case testNestedConditionalFunctionDefinitionWithIfElse]
870+
from typing import Any
871+
x = None # type: Any
872+
def top() -> None:
873+
if x:
874+
def f(x: int) -> None:
875+
x = 'x' # fail
876+
x = 1
877+
else:
878+
def f(x: int) -> None:
879+
x + 'x' # fail
880+
x = 1
881+
f(1)
882+
f('x') # fail
883+
[out]
884+
main: note: In function "f":
885+
main:6: error: Incompatible types in assignment (expression has type "str", variable has type "int")
886+
main:10: error: Unsupported left operand type for + ("int")
887+
main: note: In function "top":
888+
main:13: error: Argument 1 to "f" has incompatible type "str"; expected "int"
889+
869890
[case testUnconditionalRedefinitionOfConditionalFunction]
870891
from typing import Any
871892
x = None # type: Any
@@ -929,6 +950,17 @@ if g():
929950
f()
930951
f(1) # E: Too many arguments for "f"
931952

953+
[case testRedefineNestedFunctionDefinedAsVariableInitializedToNone]
954+
def g() -> None:
955+
f = None
956+
if object():
957+
def f(x: int) -> None: pass
958+
f() # E: Too few arguments for "f"
959+
f(1)
960+
f('') # E: Argument 1 to "f" has incompatible type "str"; expected "int"
961+
[out]
962+
main: note: In function "g":
963+
932964
[case testRedefineFunctionDefinedAsVariableWithInvalidSignature]
933965
def g(): pass
934966
f = g

0 commit comments

Comments
 (0)