From cd22b7344e8dd0c9bc1347301dd512fa6f483ff3 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Fri, 14 Oct 2016 21:12:55 -0700 Subject: [PATCH 01/21] Use a separate TypeChecker per file. --- mypy/build.py | 34 +++++++++++++++++++++------------- mypy/checker.py | 6 ------ 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/mypy/build.py b/mypy/build.py index 679c331f0ddb..a6aa0201c338 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -33,9 +33,11 @@ from mypy import moduleinfo from mypy import util from mypy.fixup import fixup_module_pass_one, fixup_module_pass_two +from mypy.nodes import Expression from mypy.options import Options from mypy.parse import parse from mypy.stats import dump_type_stats +from mypy.types import Type from mypy.version import __version__ @@ -62,7 +64,7 @@ class BuildResult: def __init__(self, manager: 'BuildManager') -> None: self.manager = manager self.files = manager.modules - self.types = manager.type_checker.type_map + self.types = manager.all_types self.errors = manager.errors.messages() @@ -184,7 +186,7 @@ def build(sources: List[BuildSource], manager.log("Build finished in %.3f seconds with %d modules, %d types, and %d errors" % (time.time() - manager.start_time, len(manager.modules), - len(manager.type_checker.type_map), + len(manager.all_types), manager.errors.num_messages())) # Finish the HTML or XML reports even if CompileError was raised. reports.finish() @@ -322,7 +324,7 @@ class BuildManager: Semantic analyzer, pass 2 semantic_analyzer_pass3: Semantic analyzer, pass 3 - type_checker: Type checker + all_types: Map {Expression: Type} collected from all modules errors: Used for reporting all errors options: Build options missing_modules: Set of modules that could not be imported encountered so far @@ -349,7 +351,7 @@ def __init__(self, data_dir: str, self.semantic_analyzer = SemanticAnalyzer(lib_path, self.errors) self.modules = self.semantic_analyzer.modules self.semantic_analyzer_pass3 = ThirdPass(self.modules, self.errors) - self.type_checker = TypeChecker(self.errors, self.modules) + self.all_types = {} # type: Dict[Expression, Type] self.indirection_detector = TypeIndirectionVisitor() self.missing_modules = set() # type: Set[str] self.stale_modules = set() # type: Set[str] @@ -461,9 +463,9 @@ def module_not_found(self, path: str, line: int, id: str) -> None: 'or using the "--silent-imports" flag would help)', severity='note', only_once=True) - def report_file(self, file: MypyFile) -> None: + def report_file(self, file: MypyFile, type_map: Dict[Expression, Type]) -> None: if self.source_set.is_source(file): - self.reports.file(file, type_map=self.type_checker.type_map) + self.reports.file(file, type_map) def log(self, *message: str) -> None: if self.options.verbosity >= 1: @@ -1412,18 +1414,24 @@ def type_check(self) -> None: if self.options.semantic_analysis_only: return with self.wrap_context(): - manager.type_checker.visit_file(self.tree, self.xpath, self.options) + type_checker = TypeChecker(manager.errors, manager.modules) + type_checker.visit_file(self.tree, self.xpath, self.options) + manager.all_types.update(type_checker.type_map) if self.options.incremental: - self._patch_indirect_dependencies(manager.type_checker.module_refs) + self._patch_indirect_dependencies(type_checker.module_refs, + type_checker.type_map) if self.options.dump_inference_stats: dump_type_stats(self.tree, self.xpath, inferred=True, - typemap=manager.type_checker.type_map) - manager.report_file(self.tree) - - def _patch_indirect_dependencies(self, module_refs: Set[str]) -> None: - types = self.manager.type_checker.module_type_map.values() + typemap=type_checker.type_map) + manager.report_file(self.tree, type_checker.type_map) + + def _patch_indirect_dependencies(self, + module_refs: Set[str], + type_map: Dict[Expression, Type]) -> None: + types = set(type_map.values()) + types.discard(None) valid = self.valid_references() encountered = self.manager.indirection_detector.find_modules(types) | module_refs diff --git a/mypy/checker.py b/mypy/checker.py index 66e8468fc338..59886698043d 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -83,8 +83,6 @@ class TypeChecker(NodeVisitor[Type]): msg = None # type: MessageBuilder # Types of type checked nodes type_map = None # type: Dict[Expression, Type] - # Types of type checked nodes within this specific module - module_type_map = None # type: Dict[Expression, Type] # Helper for managing conditional types binder = None # type: ConditionalTypeBinder @@ -130,7 +128,6 @@ def __init__(self, errors: Errors, modules: Dict[str, MypyFile]) -> None: self.modules = modules self.msg = MessageBuilder(errors, modules) self.type_map = {} - self.module_type_map = {} self.binder = ConditionalTypeBinder() self.expr_checker = mypy.checkexpr.ExpressionChecker(self, self.msg) self.return_types = [] @@ -152,7 +149,6 @@ def visit_file(self, file_node: MypyFile, path: str, options: Options) -> None: self.globals = file_node.names self.enter_partial_types() self.is_typeshed_stub = self.errors.is_typeshed_file(path) - self.module_type_map = {} self.module_refs = set() if self.options.strict_optional_whitelist is None: self.suppress_none_errors = not self.options.show_none_errors @@ -2232,8 +2228,6 @@ def check_type_equivalency(self, t1: Type, t2: Type, node: Context, def store_type(self, node: Expression, typ: Type) -> None: """Store the type of a node in the type map.""" self.type_map[node] = typ - if typ is not None: - self.module_type_map[node] = typ def in_checked_function(self) -> bool: """Should we type-check the current function? From 4bd65c05b62aed02bba1518120198d2c01c21690 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Sat, 15 Oct 2016 21:15:39 -0700 Subject: [PATCH 02/21] Change the TypeChecker toplevel interface. --- mypy/build.py | 6 ++++-- mypy/checker.py | 49 ++++++++++++++++++++++++------------------------- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/mypy/build.py b/mypy/build.py index a6aa0201c338..21f70a849d3e 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -1414,8 +1414,10 @@ def type_check(self) -> None: if self.options.semantic_analysis_only: return with self.wrap_context(): - type_checker = TypeChecker(manager.errors, manager.modules) - type_checker.visit_file(self.tree, self.xpath, self.options) + type_checker = TypeChecker(manager.errors, manager.modules, self.options, + self.tree, self.xpath) + type_checker.check_first_pass() + type_checker.check_second_pass() manager.all_types.update(type_checker.type_map) if self.options.incremental: diff --git a/mypy/checker.py b/mypy/checker.py index 59886698043d..cbebdf1eb1ca 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -73,6 +73,8 @@ class TypeChecker(NodeVisitor[Type]): """Mypy type checker. Type check mypy source files that have been semantically analyzed. + + You must create a separate instance for each source file. """ # Are we type checking a stub? @@ -119,54 +121,52 @@ class TypeChecker(NodeVisitor[Type]): # directly or indirectly. module_refs = None # type: Set[str] - def __init__(self, errors: Errors, modules: Dict[str, MypyFile]) -> None: + def __init__(self, errors: Errors, modules: Dict[str, MypyFile], options: Options, + tree: MypyFile, path: str) -> None: """Construct a type checker. Use errors to report type check errors. """ self.errors = errors self.modules = modules + self.options = options + self.tree = tree + self.path = path self.msg = MessageBuilder(errors, modules) - self.type_map = {} - self.binder = ConditionalTypeBinder() self.expr_checker = mypy.checkexpr.ExpressionChecker(self, self.msg) + self.binder = ConditionalTypeBinder() + self.globals = tree.names self.return_types = [] self.type_context = [] self.dynamic_funcs = [] self.function_stack = [] self.partial_types = [] self.deferred_nodes = [] - self.pass_num = 0 - self.current_node_deferred = False + self.type_map = {} self.module_refs = set() - - def visit_file(self, file_node: MypyFile, path: str, options: Options) -> None: - """Type check a mypy file with the given path.""" - self.options = options self.pass_num = 0 - self.is_stub = file_node.is_stub - self.errors.set_file(path) - self.globals = file_node.names - self.enter_partial_types() - self.is_typeshed_stub = self.errors.is_typeshed_file(path) - self.module_refs = set() - if self.options.strict_optional_whitelist is None: - self.suppress_none_errors = not self.options.show_none_errors + self.current_node_deferred = False + self.is_stub = tree.is_stub + self.is_typeshed_stub = errors.is_typeshed_file(path) + if options.strict_optional_whitelist is None: + self.suppress_none_errors = not options.show_none_errors else: self.suppress_none_errors = not any(fnmatch.fnmatch(path, pattern) for pattern - in self.options.strict_optional_whitelist) + in options.strict_optional_whitelist) + + def check_first_pass(self) -> None: + """Type check the entire file, but defer functions with forward references.""" + self.errors.set_file(self.path) + self.enter_partial_types() with self.binder.top_frame_context(): - for d in file_node.defs: + for d in self.tree.defs: self.accept(d) self.leave_partial_types() - if self.deferred_nodes: - self.check_second_pass() - - self.current_node_deferred = False + assert not self.current_node_deferred all_ = self.globals.get('__all__') if all_ is not None and all_.type is not None: @@ -177,10 +177,9 @@ def visit_file(self, file_node: MypyFile, path: str, options: Options) -> None: self.fail(messages.ALL_MUST_BE_SEQ_STR.format(str_seq_s, all_s), all_.node) - del self.options - def check_second_pass(self) -> None: """Run second pass of type checking which goes through deferred nodes.""" + self.errors.set_file(self.path) self.pass_num = 1 for node, type_name in self.deferred_nodes: if type_name: From 9b46001fb9b4d7850c65a98d038b882522447fc2 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Sun, 16 Oct 2016 20:14:58 -0700 Subject: [PATCH 03/21] Do deferred (second) type checker pass after all first passes in an SCC. --- mypy/build.py | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/mypy/build.py b/mypy/build.py index 21f70a849d3e..dd94a364c4a6 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -1409,25 +1409,29 @@ def semantic_analysis_pass_three(self) -> None: if self.options.dump_type_stats: dump_type_stats(self.tree, self.xpath) - def type_check(self) -> None: + def type_check_first_pass(self) -> None: manager = self.manager if self.options.semantic_analysis_only: return with self.wrap_context(): - type_checker = TypeChecker(manager.errors, manager.modules, self.options, - self.tree, self.xpath) - type_checker.check_first_pass() - type_checker.check_second_pass() - manager.all_types.update(type_checker.type_map) + self.type_checker = TypeChecker(manager.errors, manager.modules, self.options, + self.tree, self.xpath) + self.type_checker.check_first_pass() + + def type_check_second_pass(self) -> None: + manager = self.manager + with self.wrap_context(): + self.type_checker.check_second_pass() + manager.all_types.update(self.type_checker.type_map) if self.options.incremental: - self._patch_indirect_dependencies(type_checker.module_refs, - type_checker.type_map) + self._patch_indirect_dependencies(self.type_checker.module_refs, + self.type_checker.type_map) if self.options.dump_inference_stats: dump_type_stats(self.tree, self.xpath, inferred=True, - typemap=type_checker.type_map) - manager.report_file(self.tree, type_checker.type_map) + typemap=self.type_checker.type_map) + manager.report_file(self.tree, self.type_checker.type_map) def _patch_indirect_dependencies(self, module_refs: Set[str], @@ -1736,7 +1740,10 @@ def process_stale_scc(graph: Graph, scc: List[str]) -> None: for id in scc: graph[id].semantic_analysis_pass_three() for id in scc: - graph[id].type_check() + graph[id].type_check_first_pass() + for id in scc: + graph[id].type_check_second_pass() + for id in scc: graph[id].write_cache() graph[id].mark_as_rechecked() From d4d26c675b26500ba5c85418cfbc5b4ebd934b1f Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Sun, 16 Oct 2016 20:23:08 -0700 Subject: [PATCH 04/21] Add simple tests. These verify that deferral due to a cross-module dependency works. --- test-data/unit/check-modules.test | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index c25c0f168e0c..bdf2b6438fdd 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -1186,6 +1186,33 @@ reveal_type(x) main:1: error: Incompatible types in assignment (expression has type "int", variable has type "str") main:2: error: Revealed type is 'builtins.str' +[case testSymmetricImportCycle1] +import a +[file a.py] +import b +def f() -> int: + return b.x +y = 0 + 0 +[file b.py] +import a +def g() -> int: + return a.y +x = 1 + 1 + +[case testSymmetricImportCycle2] +import b +[file a.py] +import b +def f() -> int: + return b.x +y = 0 + 0 +[file b.py] +import a +def g() -> int: + return a.y +x = 1 + 1 + + -- Scripts and __main__ [case testScriptsAreModules] @@ -1202,3 +1229,4 @@ pass [file b] pass [out] + From 4afdc13bbe5848f6331c1743f3f79120c2f8c806 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Sun, 16 Oct 2016 20:40:21 -0700 Subject: [PATCH 05/21] Fix test failures due to semantic_analysis_only flag. --- mypy/build.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mypy/build.py b/mypy/build.py index dd94a364c4a6..125b624c8697 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -1420,6 +1420,8 @@ def type_check_first_pass(self) -> None: def type_check_second_pass(self) -> None: manager = self.manager + if self.options.semantic_analysis_only: + return with self.wrap_context(): self.type_checker.check_second_pass() manager.all_types.update(self.type_checker.type_map) From be3dfa1a10c9672addb4e93536cea88ac9b31e9f Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Mon, 17 Oct 2016 13:49:11 -0700 Subject: [PATCH 06/21] Add TODOs about getting rid of BuildResult and all_types. --- mypy/build.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mypy/build.py b/mypy/build.py index 125b624c8697..94b580ce342b 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -51,6 +51,7 @@ Graph = Dict[str, 'State'] +# TODO: Get rid of BuildResult. We might as well return a BuildManager. class BuildResult: """The result of a successful build. @@ -309,6 +310,8 @@ def default_lib_path(data_dir: str, pyversion: Tuple[int, int]) -> List[str]: PRI_ALL = 99 # include all priorities +# TODO: Get rid of all_types. It's not used except for one log message. +# Maybe we could instead publish a map from module ID to its type_map. class BuildManager: """This class holds shared state for building a mypy program. From de3a076eaec99a515728d2ff89eca20129da30c0 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 19 Oct 2016 12:06:23 -0700 Subject: [PATCH 07/21] Also hide cross-module notes with --hide-error-context --- mypy/errors.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mypy/errors.py b/mypy/errors.py index 541e4ca61cd2..a664177fbafb 100644 --- a/mypy/errors.py +++ b/mypy/errors.py @@ -290,7 +290,9 @@ def render_messages(self, errors: List[ErrorInfo]) -> List[Tuple[str, int, int, for e in errors: # Report module import context, if different from previous message. - if e.import_ctx != prev_import_context: + if self.hide_error_context: + pass + elif e.import_ctx != prev_import_context: last = len(e.import_ctx) - 1 i = last while i >= 0: From ed10d6d38aa6b80e069f4beb8e07cc39a07f4e8a Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 19 Oct 2016 12:06:41 -0700 Subject: [PATCH 08/21] Expand docstring for check_first_pass() --- mypy/checker.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index cbebdf1eb1ca..095c27a381b3 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -156,7 +156,15 @@ def __init__(self, errors: Errors, modules: Dict[str, MypyFile], options: Option in options.strict_optional_whitelist) def check_first_pass(self) -> None: - """Type check the entire file, but defer functions with forward references.""" + """Type check the entire file, but defer functions with unresolved references. + + Unresolved references are forward references to variables + whose types haven't been inferred yet. They may occur later + in the same file or in a different file that's being processed + later (usually due to an import cycle). + + Deferred functions will be processed by check_second_pass(). + """ self.errors.set_file(self.path) self.enter_partial_types() From 30c84fa7e63ebf527f2228af615d9491397a6a8c Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 19 Oct 2016 12:07:16 -0700 Subject: [PATCH 09/21] Suppress reveal_type() output when current node is being deferred --- mypy/checkexpr.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 6f4de4a060ea..6f264e0646b5 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1367,7 +1367,8 @@ def is_valid_cast(self, source_type: Type, target_type: Type) -> bool: def visit_reveal_type_expr(self, expr: RevealTypeExpr) -> Type: """Type check a reveal_type expression.""" revealed_type = self.accept(expr.expr) - self.msg.reveal_type(revealed_type, expr) + if not self.chk.current_node_deferred: + self.msg.reveal_type(revealed_type, expr) return revealed_type def visit_type_application(self, tapp: TypeApplication) -> Type: From f68a7d47e892be9a7a97f0dcac7142a3cf7105a9 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 19 Oct 2016 12:07:56 -0700 Subject: [PATCH 10/21] Add reveal_type() calls to testSymmetricImportCycle* --- test-data/unit/check-modules.test | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index bdf2b6438fdd..b05d1fbbae36 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -1187,6 +1187,7 @@ main:1: error: Incompatible types in assignment (expression has type "int", vari main:2: error: Revealed type is 'builtins.str' [case testSymmetricImportCycle1] +# flags: --hide-error-context import a [file a.py] import b @@ -1196,14 +1197,19 @@ y = 0 + 0 [file b.py] import a def g() -> int: + reveal_type(a.y) return a.y x = 1 + 1 +[out] +tmp/b.py:3: error: Revealed type is 'builtins.int' [case testSymmetricImportCycle2] +# flags: --hide-error-context import b [file a.py] import b def f() -> int: + reveal_type(b.x) return b.x y = 0 + 0 [file b.py] @@ -1211,6 +1217,8 @@ import a def g() -> int: return a.y x = 1 + 1 +[out] +tmp/a.py:3: error: Revealed type is 'builtins.int' -- Scripts and __main__ From fd5bc224b286f6932589477d6db087618ad1d4ac Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Thu, 20 Oct 2016 07:39:21 -0700 Subject: [PATCH 11/21] WIP skip deferred queue duplicates --- mypy/checker.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mypy/checker.py b/mypy/checker.py index 095c27a381b3..1c18c6003456 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -189,7 +189,12 @@ def check_second_pass(self) -> None: """Run second pass of type checking which goes through deferred nodes.""" self.errors.set_file(self.path) self.pass_num = 1 + done = set() # type: Set[FuncItem] for node, type_name in self.deferred_nodes: + if node in done: + continue + print(type_name, '.', node.fullname() or node.name()) + done.add(node) if type_name: self.errors.push_type(type_name) self.accept(node) From 53f635bc45bd5ca0ba9edd00cba51be34534f76c Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Fri, 21 Oct 2016 21:56:23 -0700 Subject: [PATCH 12/21] Make it easy to do more than 2 passes --- mypy/build.py | 19 +++++++++++++++---- mypy/checker.py | 22 ++++++++++++++++------ 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/mypy/build.py b/mypy/build.py index 94b580ce342b..d0c69b8d2c0b 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -1421,12 +1421,18 @@ def type_check_first_pass(self) -> None: self.tree, self.xpath) self.type_checker.check_first_pass() - def type_check_second_pass(self) -> None: + def type_check_second_pass(self) -> bool: + manager = self.manager + if self.options.semantic_analysis_only: + return False + with self.wrap_context(): + return self.type_checker.check_second_pass() + + def finish_passes(self) -> None: manager = self.manager if self.options.semantic_analysis_only: return with self.wrap_context(): - self.type_checker.check_second_pass() manager.all_types.update(self.type_checker.type_map) if self.options.incremental: @@ -1746,9 +1752,14 @@ def process_stale_scc(graph: Graph, scc: List[str]) -> None: graph[id].semantic_analysis_pass_three() for id in scc: graph[id].type_check_first_pass() + more = True + while more: + more = False + for id in scc: + if graph[id].type_check_second_pass(): + more = True for id in scc: - graph[id].type_check_second_pass() - for id in scc: + graph[id].finish_passes() graph[id].write_cache() graph[id].mark_as_rechecked() diff --git a/mypy/checker.py b/mypy/checker.py index 1c18c6003456..32f25e584a83 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -59,6 +59,8 @@ T = TypeVar('T') +LAST_PASS = 1 # Pass numbers start at 0 + # A node which is postponed to be type checked during the next pass. DeferredNode = NamedTuple( @@ -185,12 +187,20 @@ def check_first_pass(self) -> None: self.fail(messages.ALL_MUST_BE_SEQ_STR.format(str_seq_s, all_s), all_.node) - def check_second_pass(self) -> None: - """Run second pass of type checking which goes through deferred nodes.""" + def check_second_pass(self) -> bool: + """Run second or following pass of type checking. + + This goes through deferred nodes, returning True if there were any. + """ + if not self.deferred_nodes: + return False self.errors.set_file(self.path) - self.pass_num = 1 + self.pass_num += 1 + print('---', self.path, 'pass', self.pass_num + 1, '---') + todo = self.deferred_nodes + self.deferred_nodes = [] done = set() # type: Set[FuncItem] - for node, type_name in self.deferred_nodes: + for node, type_name in todo: if node in done: continue print(type_name, '.', node.fullname() or node.name()) @@ -200,10 +210,10 @@ def check_second_pass(self) -> None: self.accept(node) if type_name: self.errors.pop_type() - self.deferred_nodes = [] + return True def handle_cannot_determine_type(self, name: str, context: Context) -> None: - if self.pass_num == 0 and self.function_stack: + if self.pass_num < LAST_PASS and self.function_stack: # Don't report an error yet. Just defer. node = self.function_stack[-1] if self.errors.type_name: From 182059b3ee0c77231dc49d3e30e0536c6d59328d Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Fri, 21 Oct 2016 21:59:31 -0700 Subject: [PATCH 13/21] Add a test that would require three passes --- test-data/unit/check-modules.test | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index b05d1fbbae36..801879e7e662 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -1220,6 +1220,22 @@ x = 1 + 1 [out] tmp/a.py:3: error: Revealed type is 'builtins.int' +[case testThreePassesRequired] +# flags: --hide-error-context +import b +[file a.py] +import b +class C: + def f1(self) -> None: + self.x2 + def f2(self) -> None: + self.x2 = b.b +[file b.py] +import a +b = 1 + 1 +[out] +tmp/a.py:4: error: Cannot determine type of 'x2' + -- Scripts and __main__ From 981c36ec727e593ba4ac73610f4f323bdb19d990 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Sat, 22 Oct 2016 07:21:13 -0700 Subject: [PATCH 14/21] Make lint happy --- mypy/build.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mypy/build.py b/mypy/build.py index d0c69b8d2c0b..289a22b499bb 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -1422,7 +1422,6 @@ def type_check_first_pass(self) -> None: self.type_checker.check_first_pass() def type_check_second_pass(self) -> bool: - manager = self.manager if self.options.semantic_analysis_only: return False with self.wrap_context(): From bb5361ff84fc623de00459f5210ddc5f6ca2351f Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Sat, 22 Oct 2016 16:55:52 -0700 Subject: [PATCH 15/21] Test case for error reported by pass two --- test-data/unit/check-modules.test | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index 801879e7e662..dd2daf3175f4 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -1236,6 +1236,20 @@ b = 1 + 1 [out] tmp/a.py:4: error: Cannot determine type of 'x2' +[case testErrorInPassTwo] +# flags: --hide-error-context +import b +[file a.py] +import b +def f() -> None: + a = b.x + 1 + a + '' +[file b.py] +import a +x = 1 + 1 +[out] +tmp/a.py:4: error: Unsupported operand types for + ("int" and "str") + -- Scripts and __main__ From 8abacf1bb40d90ea9c2d5e90f5e5f46040f50c82 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Sat, 22 Oct 2016 18:08:19 -0700 Subject: [PATCH 16/21] Test case for deferred decorator --- test-data/unit/check-modules.test | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index dd2daf3175f4..dc118ac1741d 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -1186,6 +1186,8 @@ reveal_type(x) main:1: error: Incompatible types in assignment (expression has type "int", variable has type "str") main:2: error: Revealed type is 'builtins.str' +-- Tests for cross-module second_pass checking. + [case testSymmetricImportCycle1] # flags: --hide-error-context import a @@ -1250,6 +1252,22 @@ x = 1 + 1 [out] tmp/a.py:4: error: Unsupported operand types for + ("int" and "str") +[case testDeferredDecorator] +# flags: --hide-error-context +import a +[file a.py] +import b +def g() -> None: + f() +@b.deco +def f() -> int: pass +x = 1 + 1 +[file b.py] +from typing import Any +import a +def deco(f: Any) -> Any: + a.x + -- Scripts and __main__ From 6aea45aa9b432315b006cb49cf01da6290629023 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Sat, 22 Oct 2016 18:12:21 -0700 Subject: [PATCH 17/21] Explicit test case that reveal_type() shuts up in deferred function first time around --- test-data/unit/check-expressions.test | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test-data/unit/check-expressions.test b/test-data/unit/check-expressions.test index f0576dd4a47a..e6fbbf092c86 100644 --- a/test-data/unit/check-expressions.test +++ b/test-data/unit/check-expressions.test @@ -1605,6 +1605,14 @@ reveal_type("foo") # E: Argument 1 to "reveal_type" has incompatible type "str"; reveal_type = 1 1 + "foo" # E: Unsupported operand types for + ("int" and "str") +[case testRevealForward] +def f() -> None: + reveal_type(x) +x = 1 + 1 +[out] +main: note: In function "f": +main:2: error: Revealed type is 'builtins.int' + [case testEqNone] None == None [builtins fixtures/ops.pyi] From 19345a058b6e6bc890ea8f61bbb2f69e0c41e090 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Mon, 24 Oct 2016 08:20:30 -0700 Subject: [PATCH 18/21] Remove print calls --- mypy/checker.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 32f25e584a83..40cc6bbab770 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -196,14 +196,12 @@ def check_second_pass(self) -> bool: return False self.errors.set_file(self.path) self.pass_num += 1 - print('---', self.path, 'pass', self.pass_num + 1, '---') todo = self.deferred_nodes self.deferred_nodes = [] done = set() # type: Set[FuncItem] for node, type_name in todo: if node in done: continue - print(type_name, '.', node.fullname() or node.name()) done.add(node) if type_name: self.errors.push_type(type_name) From bb212eb58db587964646586aff0dc66f8eaabe1a Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Mon, 24 Oct 2016 08:25:37 -0700 Subject: [PATCH 19/21] Remove --hide-error-context from new tests and adjust expected output --- test-data/unit/check-modules.test | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index dc118ac1741d..4b0cfcd5bfaf 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -1189,7 +1189,6 @@ main:2: error: Revealed type is 'builtins.str' -- Tests for cross-module second_pass checking. [case testSymmetricImportCycle1] -# flags: --hide-error-context import a [file a.py] import b @@ -1203,10 +1202,12 @@ def g() -> int: return a.y x = 1 + 1 [out] +tmp/a.py:1: note: In module imported here, +main:1: note: ... from here: +tmp/b.py: note: In function "g": tmp/b.py:3: error: Revealed type is 'builtins.int' [case testSymmetricImportCycle2] -# flags: --hide-error-context import b [file a.py] import b @@ -1220,10 +1221,12 @@ def g() -> int: return a.y x = 1 + 1 [out] +tmp/b.py:1: note: In module imported here, +main:1: note: ... from here: +tmp/a.py: note: In function "f": tmp/a.py:3: error: Revealed type is 'builtins.int' [case testThreePassesRequired] -# flags: --hide-error-context import b [file a.py] import b @@ -1236,10 +1239,12 @@ class C: import a b = 1 + 1 [out] +tmp/b.py:1: note: In module imported here, +main:1: note: ... from here: +tmp/a.py: note: In member "f1" of class "C": tmp/a.py:4: error: Cannot determine type of 'x2' [case testErrorInPassTwo] -# flags: --hide-error-context import b [file a.py] import b @@ -1250,10 +1255,12 @@ def f() -> None: import a x = 1 + 1 [out] +tmp/b.py:1: note: In module imported here, +main:1: note: ... from here: +tmp/a.py: note: In function "f": tmp/a.py:4: error: Unsupported operand types for + ("int" and "str") [case testDeferredDecorator] -# flags: --hide-error-context import a [file a.py] import b @@ -1285,4 +1292,3 @@ pass [file b] pass [out] - From 331f845f5897c2dc5684ad6a413fe975b2c7dde8 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Mon, 24 Oct 2016 08:27:32 -0700 Subject: [PATCH 20/21] Add reversed version of testErrorInPassTwo --- test-data/unit/check-modules.test | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index 4b0cfcd5bfaf..a4bcb8d7a607 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -1244,7 +1244,7 @@ main:1: note: ... from here: tmp/a.py: note: In member "f1" of class "C": tmp/a.py:4: error: Cannot determine type of 'x2' -[case testErrorInPassTwo] +[case testErrorInPassTwo1] import b [file a.py] import b @@ -1260,6 +1260,21 @@ main:1: note: ... from here: tmp/a.py: note: In function "f": tmp/a.py:4: error: Unsupported operand types for + ("int" and "str") +[case testErrorInPassTwo2] +import a +[file a.py] +import b +def f() -> None: + a = b.x + 1 + a + '' +[file b.py] +import a +x = 1 + 1 +[out] +main:1: note: In module imported here: +tmp/a.py: note: In function "f": +tmp/a.py:4: error: Unsupported operand types for + ("int" and "str") + [case testDeferredDecorator] import a [file a.py] From d1778da9b174c418303955b760408ca1690b36ef Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Mon, 24 Oct 2016 08:31:38 -0700 Subject: [PATCH 21/21] Make decorator more complicated (both b.deco and a.g are now deferred) --- test-data/unit/check-modules.test | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index a4bcb8d7a607..d488a39d5a4d 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -1280,15 +1280,21 @@ import a [file a.py] import b def g() -> None: - f() + f('') @b.deco -def f() -> int: pass +def f(a: str) -> int: pass +reveal_type(f) x = 1 + 1 [file b.py] -from typing import Any +from typing import Callable, TypeVar import a -def deco(f: Any) -> Any: +T = TypeVar('T') +def deco(f: Callable[[T], int]) -> Callable[[T], int]: a.x + return f +[out] +main:1: note: In module imported here: +tmp/a.py:6: error: Revealed type is 'def (builtins.str*) -> builtins.int' -- Scripts and __main__