From 32a80a51a4779d30897db6acf284ee510bc20e92 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 20 Apr 2016 15:54:08 -0700 Subject: [PATCH 1/4] Suppress errors from semantic analysis in dynamic (unannotated) functions. Fix #1334. --- mypy/build.py | 6 ++++-- mypy/semanal.py | 14 +++++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/mypy/build.py b/mypy/build.py index 3aeb265ac3e6..d075fb493679 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -368,8 +368,10 @@ def __init__(self, data_dir: str, self.custom_typing_module = custom_typing_module self.source_set = source_set self.reports = reports + check_untyped_defs = CHECK_UNTYPED_DEFS in self.flags self.semantic_analyzer = SemanticAnalyzer(lib_path, self.errors, - pyversion=pyversion) + pyversion=pyversion, + check_untyped_defs=check_untyped_defs) self.modules = self.semantic_analyzer.modules self.semantic_analyzer_pass3 = ThirdPass(self.modules, self.errors) self.type_checker = TypeChecker(self.errors, @@ -377,7 +379,7 @@ def __init__(self, data_dir: str, self.pyversion, DISALLOW_UNTYPED_CALLS in self.flags, DISALLOW_UNTYPED_DEFS in self.flags, - CHECK_UNTYPED_DEFS in self.flags) + check_untyped_defs) self.missing_modules = set() # type: Set[str] def all_imported_modules_in_file(self, diff --git a/mypy/semanal.py b/mypy/semanal.py index 2be14397ded8..8aece9190c37 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -191,7 +191,8 @@ class SemanticAnalyzer(NodeVisitor): errors = None # type: Errors # Keeps track of generated errors def __init__(self, lib_path: List[str], errors: Errors, - pyversion: Tuple[int, int] = defaults.PYTHON3_VERSION) -> None: + pyversion: Tuple[int, int] = defaults.PYTHON3_VERSION, + check_untyped_defs: bool = False) -> None: """Construct semantic analyzer. Use lib_path to search for modules, and report analysis errors @@ -211,6 +212,7 @@ def __init__(self, lib_path: List[str], errors: Errors, self.errors = errors self.modules = {} self.pyversion = pyversion + self.check_untyped_defs = check_untyped_defs self.postpone_nested_functions_stack = [FUNCTION_BOTH_PHASES] self.postponed_functions_stack = [] @@ -244,10 +246,12 @@ def visit_file(self, file_node: MypyFile, fnam: str) -> None: def visit_func_def(self, defn: FuncDef) -> None: phase_info = self.postpone_nested_functions_stack[-1] if phase_info != FUNCTION_SECOND_PHASE: + self.function_stack.append(defn) # First phase of analysis for function. self.errors.push_function(defn.name()) self.update_function_type_variables(defn) self.errors.pop_function() + self.function_stack.pop() defn.is_conditional = self.block_depth[-1] > 0 @@ -2204,9 +2208,17 @@ def name_already_defined(self, name: str, ctx: Context) -> None: self.fail("Name '{}' already defined".format(name), ctx) def fail(self, msg: str, ctx: Context) -> None: + if (self.function_stack and + self.function_stack[-1].is_dynamic() and + not self.check_untyped_defs): + return self.errors.report(ctx.get_line(), msg) def note(self, msg: str, ctx: Context) -> None: + if (self.function_stack and + self.function_stack[-1].is_dynamic() and + not self.check_untyped_defs): + return self.errors.report(ctx.get_line(), msg, severity='note') def undefined_name_extra_info(self, fullname: str) -> Optional[str]: From 7f9057f8e0336992a69badbbe9f6702b70298a9e Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 20 Apr 2016 20:48:43 -0700 Subject: [PATCH 2/4] Don't suppress 'continue/break' outside loop. --- mypy/semanal.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 8aece9190c37..17300b45ce43 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1634,11 +1634,11 @@ def visit_for_stmt(self, s: ForStmt) -> None: def visit_break_stmt(self, s: BreakStmt) -> None: if self.loop_depth == 0: - self.fail("'break' outside loop", s) + self.fail("'break' outside loop", s, True) def visit_continue_stmt(self, s: ContinueStmt) -> None: if self.loop_depth == 0: - self.fail("'continue' outside loop", s) + self.fail("'continue' outside loop", s, True) def visit_if_stmt(self, s: IfStmt) -> None: infer_reachability_of_if_statement(s, pyversion=self.pyversion) @@ -2207,17 +2207,18 @@ def name_not_defined(self, name: str, ctx: Context) -> None: def name_already_defined(self, name: str, ctx: Context) -> None: self.fail("Name '{}' already defined".format(name), ctx) - def fail(self, msg: str, ctx: Context) -> None: - if (self.function_stack and - self.function_stack[-1].is_dynamic() and - not self.check_untyped_defs): + def fail(self, msg: str, ctx: Context, serious: bool = False) -> None: + if (not serious and + not self.check_untyped_defs and + self.function_stack and + self.function_stack[-1].is_dynamic()): return self.errors.report(ctx.get_line(), msg) def note(self, msg: str, ctx: Context) -> None: - if (self.function_stack and - self.function_stack[-1].is_dynamic() and - not self.check_untyped_defs): + if (not self.check_untyped_defs and + self.function_stack and + self.function_stack[-1].is_dynamic()): return self.errors.report(ctx.get_line(), msg, severity='note') From 350afbb592d1b744208ad72185dffbe72bf314b8 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 20 Apr 2016 20:49:25 -0700 Subject: [PATCH 3/4] Rough and tumble fix to make tests pass. Not sure all these are right. Please review. --- mypy/test/data/check-weak-typing.test | 8 ------ mypy/test/data/semanal-errors.test | 38 +++++++++++++------------- mypy/test/data/semanal-statements.test | 2 +- 3 files changed, 20 insertions(+), 28 deletions(-) diff --git a/mypy/test/data/check-weak-typing.test b/mypy/test/data/check-weak-typing.test index a6bb1fc6ef14..35b4b73dd382 100644 --- a/mypy/test/data/check-weak-typing.test +++ b/mypy/test/data/check-weak-typing.test @@ -123,14 +123,6 @@ def f(): 1 + 'a' # E: Unsupported left operand type for + ("int") [out] main: note: In function "f": -[case testNonWeakFunction] -def f(): - if y: # E: Name 'y' is not defined - x = 1 - else: - x = 'a' -[out] -main: note: In function "f": [case testWeakFunctionCall] # mypy: weak=global def f(a: str) -> None: pass diff --git a/mypy/test/data/semanal-errors.test b/mypy/test/data/semanal-errors.test index c083d3416d13..c8d2e1dcb11d 100644 --- a/mypy/test/data/semanal-errors.test +++ b/mypy/test/data/semanal-errors.test @@ -17,7 +17,7 @@ main:3: error: Name 'y' is not defined [case testUndefinedVariableWithinFunctionContext] import typing -def f(): +def f() -> None: x y [out] @@ -39,7 +39,7 @@ import typing class A: def f(self): pass class B: - def g(self): + def g(self) -> None: f # error g # error [out] @@ -178,7 +178,7 @@ main:4: error: Name 'x' already defined [case testLocalVarRedefinition] import typing class A: pass -def f(): +def f() -> None: x = 0 # type: A x = 0 # type: A [out] @@ -523,7 +523,7 @@ main:4: error: Invalid type "__main__.t" from typing import TypeVar, Generic t = TypeVar('t') class c(Generic[t]): - def f(self): x = t + def f(self) -> None: x = t def f(y: t): x = t [out] main: note: In function "f": @@ -546,7 +546,7 @@ main:2: error: Name 'B' is not defined [case testSuperOutsideClass] class A: pass super().x -def f(): super().y +def f() -> None: super().y [out] main:2: error: "super" used outside class main: note: In function "f": @@ -572,7 +572,7 @@ main:5: error: Name 'f' already defined [case testInvalidGlobalDecl] import typing -def f(): +def f() -> None: global x x = None [out] @@ -582,7 +582,7 @@ main:4: error: Name 'x' is not defined [case testInvalidNonlocalDecl] import typing def f(): - def g(): + def g() -> None: nonlocal x x = None [out] @@ -593,7 +593,7 @@ main:5: error: Name 'x' is not defined [case testNonlocalDeclNotMatchingGlobal] import typing x = None -def f(): +def f() -> None: nonlocal x x = None [out] @@ -605,7 +605,7 @@ main:5: error: Name 'x' is not defined import typing def g(): x = None - def f(x): + def f(x) -> None: nonlocal x x = None [out] @@ -623,7 +623,7 @@ import typing x = 1 def f(): x = 1 - def g(): + def g() -> None: global x nonlocal x x = None @@ -636,7 +636,7 @@ import typing x = 1 def f(): x = 1 - def g(): + def g() -> None: nonlocal x global x x = None @@ -646,7 +646,7 @@ main:7: error: Name 'x' is nonlocal and global [case testNestedFunctionAndScoping] import typing -def f(x): +def f(x) -> None: def g(y): z = x z @@ -659,7 +659,7 @@ main:6: error: Name 'y' is not defined [case testMultipleNestedFunctionDef] import typing -def f(x): +def f(x) -> None: def g(): pass x = 1 def g(): pass @@ -669,7 +669,7 @@ main:5: error: Name 'g' already defined [case testRedefinedOverloadedFunction] from typing import overload, Any -def f(): +def f() -> None: @overload def p(o: object) -> None: pass # no error @overload @@ -683,8 +683,8 @@ main:8: error: Name 'p' already defined [case testNestedFunctionInMethod] import typing class A: - def f(self): - def g(): + def f(self) -> None: + def g() -> None: x y [out] @@ -861,7 +861,7 @@ main:3: error: 'abstractmethod' used with a non-method [case testAbstractNestedFunction] import typing from abc import abstractmethod -def g(): +def g() -> None: @abstractmethod def foo(): pass [out] @@ -1133,7 +1133,7 @@ import typing @staticmethod def f(): pass class A: - def g(self): + def g(self) -> None: @staticmethod def h(): pass [builtins fixtures/staticmethod.py] @@ -1147,7 +1147,7 @@ import typing @classmethod def f(): pass class A: - def g(self): + def g(self) -> None: @classmethod def h(): pass [builtins fixtures/classmethod.py] diff --git a/mypy/test/data/semanal-statements.test b/mypy/test/data/semanal-statements.test index 7c216e476724..bc8614c10214 100644 --- a/mypy/test/data/semanal-statements.test +++ b/mypy/test/data/semanal-statements.test @@ -534,7 +534,7 @@ MypyFile:1( IntExpr(0))))))) [case testDelMultipleThingsInvalid] -def f(x, y): +def f(x, y) -> None: del x, y + 1 [out] main: note: In function "f": From a09a5e0e77ac21a894977076472bde224a2b4251 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Thu, 21 Apr 2016 09:44:19 -0700 Subject: [PATCH 4/4] Clean up SemanticAnalyzer.__init__(). --- mypy/semanal.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 17300b45ce43..3146643aa549 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -190,9 +190,11 @@ class SemanticAnalyzer(NodeVisitor): imports = None # type: Set[str] # Imported modules (during phase 2 analysis) errors = None # type: Errors # Keeps track of generated errors - def __init__(self, lib_path: List[str], errors: Errors, - pyversion: Tuple[int, int] = defaults.PYTHON3_VERSION, - check_untyped_defs: bool = False) -> None: + def __init__(self, + lib_path: List[str], + errors: Errors, + pyversion: Tuple[int, int], + check_untyped_defs: bool) -> None: """Construct semantic analyzer. Use lib_path to search for modules, and report analysis errors