From 91faa267c4fac3c9629c37f8b47e968007a8918c Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Tue, 25 Oct 2016 08:53:46 -0700 Subject: [PATCH] Fix bug with exception variable reuse in deferred node. (#2290) * Fix bug with exception variable reuse in deferred node. The fix is actually by @ecprice. See https://github.com/python/mypy/pull/1748#issuecomment-254927514 --- mypy/checker.py | 7 ++ test-data/unit/check-statements.test | 104 +++++++++++++++++++++++++++ 2 files changed, 111 insertions(+) diff --git a/mypy/checker.py b/mypy/checker.py index 52a6774890f4..513a6930a382 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1680,7 +1680,14 @@ def visit_try_without_finally(self, s: TryStmt, try_frame: bool) -> None: # To support local variables, we make this a definition line, # causing assignment to set the variable's type. s.vars[i].is_def = True + # We also temporarily set current_node_deferred to False to + # make sure the inference happens. + # TODO: Use a better solution, e.g. a + # separate Var for each except block. + am_deferring = self.current_node_deferred + self.current_node_deferred = False self.check_assignment(s.vars[i], self.temp_node(t, s.vars[i])) + self.current_node_deferred = am_deferring self.accept(s.handlers[i]) if s.vars[i]: # Exception variables are deleted in python 3 but not python 2. diff --git a/test-data/unit/check-statements.test b/test-data/unit/check-statements.test index 4de0d72ae59b..be470ea1964b 100644 --- a/test-data/unit/check-statements.test +++ b/test-data/unit/check-statements.test @@ -687,6 +687,110 @@ e = 1 # E: Assignment to variable 'e' outside except: block e = E1() # E: Assignment to variable 'e' outside except: block [builtins fixtures/exception.pyi] +[case testExceptionVariableReuseInDeferredNode1] +def f(*a: BaseException) -> int: + x + try: pass + except BaseException as err: pass + try: pass + except BaseException as err: f(err) +x = f() +[builtins fixtures/exception.pyi] + +[case testExceptionVariableReuseInDeferredNode2] +def f(*a: BaseException) -> int: + try: pass + except BaseException as err: pass + x + try: pass + except BaseException as err: f(err) +x = f() +[builtins fixtures/exception.pyi] + +[case testExceptionVariableReuseInDeferredNode3] +def f(*a: BaseException) -> int: + try: pass + except BaseException as err: pass + try: pass + except BaseException as err: f(err) + x +x = f() +[builtins fixtures/exception.pyi] + +[case testExceptionVariableReuseInDeferredNode4] +class EA(BaseException): + a = None # type: int +class EB(BaseException): + b = None # type: str +def f(*arg: BaseException) -> int: + x + try: pass + except EA as err: + f(err) + a = err.a + reveal_type(a) + try: pass + except EB as err: + f(err) + b = err.b + reveal_type(b) +x = f() +[builtins fixtures/exception.pyi] +[out] +main: note: In function "f": +main:11: error: Revealed type is 'builtins.int' +main:16: error: Revealed type is 'builtins.str' + +[case testExceptionVariableReuseInDeferredNode5] +class EA(BaseException): + a = None # type: int +class EB(BaseException): + b = None # type: str +def f(*arg: BaseException) -> int: + try: pass + except EA as err: + f(err) + a = err.a + reveal_type(a) + x + try: pass + except EB as err: + f(err) + b = err.b + reveal_type(b) +x = f() +[builtins fixtures/exception.pyi] +[out] +main: note: In function "f": +main:10: error: Revealed type is 'builtins.int' +main:16: error: Revealed type is 'builtins.str' + +[case testExceptionVariableReuseInDeferredNode6] +class EA(BaseException): + a = None # type: int +class EB(BaseException): + b = None # type: str +def f(*arg: BaseException) -> int: + try: pass + except EA as err: + f(err) + a = err.a + reveal_type(a) + try: pass + except EB as err: + f(err) + b = err.b + reveal_type(b) + x +x = f() +[builtins fixtures/exception.pyi] +[out] +main: note: In function "f": +main:10: error: Revealed type is 'builtins.int' +main:15: error: Revealed type is 'builtins.str' + + + [case testArbitraryExpressionAsExceptionType] import typing a = BaseException