From ffd8297d51ad0637f5f92fd67f30210c7bfe7142 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Wed, 6 Feb 2019 16:31:11 +0000 Subject: [PATCH 01/20] Add a test --- mypy/test/testpythoneval.py | 2 ++ test-data/unit/pythoneval.test | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/mypy/test/testpythoneval.py b/mypy/test/testpythoneval.py index 8ba4566b797dc..cef8a59c2510d 100644 --- a/mypy/test/testpythoneval.py +++ b/mypy/test/testpythoneval.py @@ -56,6 +56,8 @@ def test_python_evaluation(testcase: DataDrivenTestCase, cache_dir: str) -> None '--no-strict-optional', '--no-silence-site-packages', ] + if testcase.name.lower().endswith('_newsemanal'): + mypy_cmdline.append('--new-semantic-analyzer') py2 = testcase.name.lower().endswith('python2') if py2: mypy_cmdline.append('--py2') diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test index 534b5b8676d0d..790e81dfbe4c1 100644 --- a/test-data/unit/pythoneval.test +++ b/test-data/unit/pythoneval.test @@ -1358,3 +1358,10 @@ x = X(a=1, b='s') [out] _testNamedTupleNew.py:12: error: Revealed type is 'Tuple[builtins.int, fallback=_testNamedTupleNew.Child]' + +[case testNewAnalyzerBasicTypeshed_newsemanal] +from typing import Dict, List + +x: Dict[str, int] +reveal_type(x['test'][0]) +[out] From b8fe2be2609c1a7990f636c5638567bfe0875ecc Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Wed, 6 Feb 2019 17:08:59 +0000 Subject: [PATCH 02/20] Try warm-up --- mypy/newsemanal/semanal.py | 2 +- mypy/newsemanal/semanal_main.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/mypy/newsemanal/semanal.py b/mypy/newsemanal/semanal.py index 0ecf9b338974f..165e973a69dfb 100644 --- a/mypy/newsemanal/semanal.py +++ b/mypy/newsemanal/semanal.py @@ -172,7 +172,7 @@ # Special cased built-in classes that are needed for basic functionality and need to be # available very early on. -CORE_BUILTIN_CLASSES = ['object', 'bool', 'function'] # type: Final +CORE_BUILTIN_CLASSES = ['object', 'bool', 'function', 'tuple'] # type: Final # Used for tracking incomplete references diff --git a/mypy/newsemanal/semanal_main.py b/mypy/newsemanal/semanal_main.py index aca219814d70f..63c2c30af2510 100644 --- a/mypy/newsemanal/semanal_main.py +++ b/mypy/newsemanal/semanal_main.py @@ -39,6 +39,8 @@ # Perform up to this many semantic analysis iterations until giving up trying to bind all names. MAX_ITERATIONS = 10 +CORE_WARMUP = 2 + def semantic_analysis_for_scc(graph: 'Graph', scc: List[str]) -> None: """Perform semantic analysis for all modules in a SCC (import cycle). @@ -65,6 +67,10 @@ def process_top_levels(graph: 'Graph', scc: List[str]) -> None: state.manager.incomplete_namespaces.update(scc) worklist = scc[:] + # HACK: process core stuff first. + if 'builtins' in worklist: + assert 'typing' in worklist and 'abc' in worklist + worklist = ['builtins', 'typing', 'abc'] * CORE_WARMUP + worklist iteration = 0 final_iteration = False while worklist: From c37285f886ab6543913bb2970c44a773fda009b3 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Wed, 6 Feb 2019 17:45:45 +0000 Subject: [PATCH 03/20] More experimentation --- mypy/newsemanal/semanal_main.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mypy/newsemanal/semanal_main.py b/mypy/newsemanal/semanal_main.py index 413d7d8cf5859..32068ed8f085d 100644 --- a/mypy/newsemanal/semanal_main.py +++ b/mypy/newsemanal/semanal_main.py @@ -39,7 +39,7 @@ # Perform up to this many semantic analysis iterations until giving up trying to bind all names. MAX_ITERATIONS = 10 -CORE_WARMUP = 2 +CORE_WARMUP = 3 def semantic_analysis_for_scc(graph: 'Graph', scc: List[str]) -> None: @@ -69,8 +69,9 @@ def process_top_levels(graph: 'Graph', scc: List[str]) -> None: worklist = scc[:] # HACK: process core stuff first. if 'builtins' in worklist: + print('Adding stuff') assert 'typing' in worklist and 'abc' in worklist - worklist = ['builtins', 'typing', 'abc'] * CORE_WARMUP + worklist + worklist += ['abc', 'builtins', 'typing'] * CORE_WARMUP iteration = 0 final_iteration = False while worklist: @@ -86,6 +87,7 @@ def process_top_levels(graph: 'Graph', scc: List[str]) -> None: next_id = worklist.pop() state = graph[next_id] assert state.tree is not None + print('processing', next_id) deferred, incomplete = semantic_analyze_target(next_id, state, state.tree, None, final_iteration) all_deferred += deferred From 94b2f305dbdc60dc8d0fd8e7db2a63cf773140d1 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Wed, 6 Feb 2019 18:56:59 +0000 Subject: [PATCH 04/20] A fix attempt --- mypy/newsemanal/semanal.py | 4 +++- mypy/newsemanal/semanal_main.py | 9 ++++----- test-data/unit/pythoneval.test | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/mypy/newsemanal/semanal.py b/mypy/newsemanal/semanal.py index 96def3e10e5c2..569a84facebc8 100644 --- a/mypy/newsemanal/semanal.py +++ b/mypy/newsemanal/semanal.py @@ -3782,8 +3782,10 @@ def add_builtin_aliases(self, tree: MypyFile) -> None: assert tree.fullname() == 'typing' for alias, target_name in type_aliases.items(): name = alias.split('.')[-1] - if name in tree.names and not isinstance(tree.names[name].node, PlaceholderNode): + if name in tree.names and not isinstance(tree.names[name].node, (Var, PlaceholderNode)): continue + if name in tree.names and isinstance(tree.names[name].node, Var): + del tree.names[name] tag = self.track_incomplete_refs() n = self.lookup_fully_qualified_or_none(target_name) if n: diff --git a/mypy/newsemanal/semanal_main.py b/mypy/newsemanal/semanal_main.py index 32068ed8f085d..b95032547e4b2 100644 --- a/mypy/newsemanal/semanal_main.py +++ b/mypy/newsemanal/semanal_main.py @@ -39,7 +39,8 @@ # Perform up to this many semantic analysis iterations until giving up trying to bind all names. MAX_ITERATIONS = 10 -CORE_WARMUP = 3 +CORE_WARMUP = 2 +core_modules = ['typing', 'builtins', 'abc', 'collections'] def semantic_analysis_for_scc(graph: 'Graph', scc: List[str]) -> None: @@ -68,10 +69,8 @@ def process_top_levels(graph: 'Graph', scc: List[str]) -> None: worklist = scc[:] # HACK: process core stuff first. - if 'builtins' in worklist: - print('Adding stuff') - assert 'typing' in worklist and 'abc' in worklist - worklist += ['abc', 'builtins', 'typing'] * CORE_WARMUP + if all(m in worklist for m in core_modules): + worklist += list(reversed(core_modules)) * CORE_WARMUP iteration = 0 final_iteration = False while worklist: diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test index 790e81dfbe4c1..b4c2c8110e84b 100644 --- a/test-data/unit/pythoneval.test +++ b/test-data/unit/pythoneval.test @@ -1362,6 +1362,6 @@ _testNamedTupleNew.py:12: error: Revealed type is 'Tuple[builtins.int, fallback= [case testNewAnalyzerBasicTypeshed_newsemanal] from typing import Dict, List -x: Dict[str, int] +x: Dict[str, List[int]] reveal_type(x['test'][0]) [out] From 358b314a37088d1c59893f5fd1134dfce7010504 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 7 Feb 2019 00:22:32 +0000 Subject: [PATCH 05/20] More experiments --- mypy/build.py | 3 +++ mypy/newsemanal/semanal.py | 8 +++++--- mypy/newsemanal/semanal_main.py | 1 - test-data/unit/pythoneval.test | 6 +++++- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/mypy/build.py b/mypy/build.py index aa9cd292682fa..09ee4301b9313 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -2558,6 +2558,9 @@ def process_graph(graph: Graph, manager: BuildManager) -> None: if 'builtins' in ascc: scc.remove('builtins') scc.append('builtins') + if 'typing' in ascc: + scc.remove('typing') + scc.insert(0, 'typing') if manager.options.verbosity >= 2: for id in scc: manager.trace("Priorities for %s:" % id, diff --git a/mypy/newsemanal/semanal.py b/mypy/newsemanal/semanal.py index be9270eea1f04..6acf68a7d23f9 100644 --- a/mypy/newsemanal/semanal.py +++ b/mypy/newsemanal/semanal.py @@ -2181,8 +2181,10 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None: return s.is_alias_def = True node = self.lookup(lvalue.name, lvalue) - assert node is not None - assert node.node is not None + if node is None: + node = SymbolTableNode(GDEF, Var(lvalue.name)) + self.add_symbol_table_node(lvalue.name, node) + # assert node.node is not None self.add_type_alias_deps(depends_on) # In addition to the aliases used, we add deps on unbound # type variables, since they are erased from target type. @@ -2210,7 +2212,7 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None: s.rvalue.analyzed.column = res.column elif isinstance(s.rvalue, RefExpr): s.rvalue.is_alias_rvalue = True - node.node = TypeAlias(res, node.node.fullname(), s.line, s.column, + node.node = TypeAlias(res, self.qualified_name(lvalue.name), s.line, s.column, alias_tvars=alias_tvars, no_args=no_args) if isinstance(rvalue, RefExpr) and isinstance(rvalue.node, TypeAlias): node.node.normalized = rvalue.node.normalized diff --git a/mypy/newsemanal/semanal_main.py b/mypy/newsemanal/semanal_main.py index b95032547e4b2..7e462f77ea6f0 100644 --- a/mypy/newsemanal/semanal_main.py +++ b/mypy/newsemanal/semanal_main.py @@ -86,7 +86,6 @@ def process_top_levels(graph: 'Graph', scc: List[str]) -> None: next_id = worklist.pop() state = graph[next_id] assert state.tree is not None - print('processing', next_id) deferred, incomplete = semantic_analyze_target(next_id, state, state.tree, None, final_iteration) all_deferred += deferred diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test index b4c2c8110e84b..6e93e4b38b2bd 100644 --- a/test-data/unit/pythoneval.test +++ b/test-data/unit/pythoneval.test @@ -1360,8 +1360,12 @@ x = X(a=1, b='s') _testNamedTupleNew.py:12: error: Revealed type is 'Tuple[builtins.int, fallback=_testNamedTupleNew.Child]' [case testNewAnalyzerBasicTypeshed_newsemanal] -from typing import Dict, List +from typing import Dict, List, Tuple x: Dict[str, List[int]] reveal_type(x['test'][0]) + +_t = Tuple[int, int] +y: _t +reveal_type(y) [out] From 75ccdf14aa63dd4f999b1a0ed967413990fa052d Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 7 Feb 2019 00:46:43 +0000 Subject: [PATCH 06/20] Some cleanups after experiments --- mypy/newsemanal/semanal.py | 8 +++----- mypy/test/hacks.py | 1 - mypy/test/testpythoneval.py | 2 -- test-data/unit/pythoneval.test | 11 ----------- 4 files changed, 3 insertions(+), 19 deletions(-) diff --git a/mypy/newsemanal/semanal.py b/mypy/newsemanal/semanal.py index 6acf68a7d23f9..be9270eea1f04 100644 --- a/mypy/newsemanal/semanal.py +++ b/mypy/newsemanal/semanal.py @@ -2181,10 +2181,8 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None: return s.is_alias_def = True node = self.lookup(lvalue.name, lvalue) - if node is None: - node = SymbolTableNode(GDEF, Var(lvalue.name)) - self.add_symbol_table_node(lvalue.name, node) - # assert node.node is not None + assert node is not None + assert node.node is not None self.add_type_alias_deps(depends_on) # In addition to the aliases used, we add deps on unbound # type variables, since they are erased from target type. @@ -2212,7 +2210,7 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None: s.rvalue.analyzed.column = res.column elif isinstance(s.rvalue, RefExpr): s.rvalue.is_alias_rvalue = True - node.node = TypeAlias(res, self.qualified_name(lvalue.name), s.line, s.column, + node.node = TypeAlias(res, node.node.fullname(), s.line, s.column, alias_tvars=alias_tvars, no_args=no_args) if isinstance(rvalue, RefExpr) and isinstance(rvalue.node, TypeAlias): node.node.normalized = rvalue.node.normalized diff --git a/mypy/test/hacks.py b/mypy/test/hacks.py index 1cb5410f2383f..7f7fa910c5de7 100644 --- a/mypy/test/hacks.py +++ b/mypy/test/hacks.py @@ -26,7 +26,6 @@ 'check-flags.test', 'check-functions.test', 'check-generics.test', - 'check-ignore.test', 'check-incomplete-fixture.test', 'check-incremental.test', 'check-inference-context.test', diff --git a/mypy/test/testpythoneval.py b/mypy/test/testpythoneval.py index cef8a59c2510d..8ba4566b797dc 100644 --- a/mypy/test/testpythoneval.py +++ b/mypy/test/testpythoneval.py @@ -56,8 +56,6 @@ def test_python_evaluation(testcase: DataDrivenTestCase, cache_dir: str) -> None '--no-strict-optional', '--no-silence-site-packages', ] - if testcase.name.lower().endswith('_newsemanal'): - mypy_cmdline.append('--new-semantic-analyzer') py2 = testcase.name.lower().endswith('python2') if py2: mypy_cmdline.append('--py2') diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test index 6e93e4b38b2bd..534b5b8676d0d 100644 --- a/test-data/unit/pythoneval.test +++ b/test-data/unit/pythoneval.test @@ -1358,14 +1358,3 @@ x = X(a=1, b='s') [out] _testNamedTupleNew.py:12: error: Revealed type is 'Tuple[builtins.int, fallback=_testNamedTupleNew.Child]' - -[case testNewAnalyzerBasicTypeshed_newsemanal] -from typing import Dict, List, Tuple - -x: Dict[str, List[int]] -reveal_type(x['test'][0]) - -_t = Tuple[int, int] -y: _t -reveal_type(y) -[out] From f63df1bbf28211d9c8a2810b823e444421c6065e Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 7 Feb 2019 11:55:11 +0000 Subject: [PATCH 07/20] Even more experiments --- mypy/build.py | 9 +++++---- mypy/checker.py | 4 ++-- mypy/test/hacks.py | 4 ++-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/mypy/build.py b/mypy/build.py index 09ee4301b9313..433ad6222236f 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -1729,7 +1729,8 @@ def patch_dependency_parents(self) -> None: SCCs. """ for dep in self.dependencies: - self.manager.semantic_analyzer.add_submodules_to_parent_modules(dep, True) + if not self.options.new_semantic_analyzer: + self.manager.semantic_analyzer.add_submodules_to_parent_modules(dep, True) def fix_suppressed_dependencies(self, graph: Graph) -> None: """Corrects whether dependencies are considered stale in silent mode. @@ -2558,9 +2559,9 @@ def process_graph(graph: Graph, manager: BuildManager) -> None: if 'builtins' in ascc: scc.remove('builtins') scc.append('builtins') - if 'typing' in ascc: - scc.remove('typing') - scc.insert(0, 'typing') + if 'abc' in ascc: + scc.remove('abc') + scc.insert(0, 'abc') if manager.options.verbosity >= 2: for id in scc: manager.trace("Priorities for %s:" % id, diff --git a/mypy/checker.py b/mypy/checker.py index 525726e42f05d..7559186964f53 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1524,8 +1524,6 @@ def erase_override(t: Type) -> Type: def visit_class_def(self, defn: ClassDef) -> None: """Type check a class definition.""" typ = defn.info - if typ.is_protocol and typ.defn.type_vars: - self.check_protocol_variance(defn) for base in typ.mro[1:]: if base.is_final: self.fail(message_registry.CANNOT_INHERIT_FROM_FINAL.format(base.name()), defn) @@ -1562,6 +1560,8 @@ def visit_class_def(self, defn: ClassDef) -> None: callable_name=fullname) # TODO: Apply the sig to the actual TypeInfo so we can handle decorators # that completely swap out the type. (e.g. Callable[[Type[A]], Type[B]]) + if typ.is_protocol and typ.defn.type_vars: + self.check_protocol_variance(defn) def check_protocol_variance(self, defn: ClassDef) -> None: """Check that protocol definition is compatible with declared diff --git a/mypy/test/hacks.py b/mypy/test/hacks.py index 7f7fa910c5de7..4674743ad3d07 100644 --- a/mypy/test/hacks.py +++ b/mypy/test/hacks.py @@ -6,8 +6,8 @@ from typing_extensions import Final # Files to not run with new semantic analyzer. -new_semanal_blacklist = [ - 'check-abstract.test', +new_semanal_blacklist = [] +['check-abstract.test', 'check-async-await.test', 'check-attr.test', 'check-basic.test', From 78aa606e0064bff76ad5329236fe4e04aef7def1 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 7 Feb 2019 15:41:20 +0000 Subject: [PATCH 08/20] Use hacks --- mypy/build.py | 8 +++++--- mypy/newsemanal/semanal.py | 2 +- mypy/newsemanal/typeanal.py | 8 ++++++++ mypy/test/testpythoneval.py | 2 ++ test-data/unit/pythoneval.test | 8 ++++++++ 5 files changed, 24 insertions(+), 4 deletions(-) diff --git a/mypy/build.py b/mypy/build.py index 433ad6222236f..05c30cae7f7a1 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -2559,9 +2559,11 @@ def process_graph(graph: Graph, manager: BuildManager) -> None: if 'builtins' in ascc: scc.remove('builtins') scc.append('builtins') - if 'abc' in ascc: - scc.remove('abc') - scc.insert(0, 'abc') + # HACK: similar is needed for 'typing', for untangling the builtins SCC when new semantic + # analyzer is used. + if 'typing' in ascc: + scc.remove('typing') + scc.insert(0, 'typing') if manager.options.verbosity >= 2: for id in scc: manager.trace("Priorities for %s:" % id, diff --git a/mypy/newsemanal/semanal.py b/mypy/newsemanal/semanal.py index be9270eea1f04..91a6718c0da4b 100644 --- a/mypy/newsemanal/semanal.py +++ b/mypy/newsemanal/semanal.py @@ -172,7 +172,7 @@ # Special cased built-in classes that are needed for basic functionality and need to be # available very early on. -CORE_BUILTIN_CLASSES = ['object', 'bool', 'function', 'tuple'] # type: Final +CORE_BUILTIN_CLASSES = ['object', 'bool', 'function'] # type: Final # Used for tracking incomplete references diff --git a/mypy/newsemanal/typeanal.py b/mypy/newsemanal/typeanal.py index 22b5b5263277c..a253a2a825afd 100644 --- a/mypy/newsemanal/typeanal.py +++ b/mypy/newsemanal/typeanal.py @@ -285,6 +285,14 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Opt " in a variable annotation", t) return AnyType(TypeOfAny.from_error) elif fullname == 'typing.Tuple': + # Tuple is special because it is involved in builtin import cycle. + try: + sym = self.api.lookup_fully_qualified('builtins.tuple') + except KeyError: + sym = None + if not sym or isinstance(sym.node, PlaceholderNode): + self.api.record_incomplete_ref() + return AnyType(TypeOfAny.special_form) if len(t.args) == 0 and not t.empty_tuple_index: # Bare 'Tuple' is same as 'tuple' if self.options.disallow_any_generics and not self.is_typeshed_stub: diff --git a/mypy/test/testpythoneval.py b/mypy/test/testpythoneval.py index 8ba4566b797dc..cef8a59c2510d 100644 --- a/mypy/test/testpythoneval.py +++ b/mypy/test/testpythoneval.py @@ -56,6 +56,8 @@ def test_python_evaluation(testcase: DataDrivenTestCase, cache_dir: str) -> None '--no-strict-optional', '--no-silence-site-packages', ] + if testcase.name.lower().endswith('_newsemanal'): + mypy_cmdline.append('--new-semantic-analyzer') py2 = testcase.name.lower().endswith('python2') if py2: mypy_cmdline.append('--py2') diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test index 534b5b8676d0d..3c7df911ab4e9 100644 --- a/test-data/unit/pythoneval.test +++ b/test-data/unit/pythoneval.test @@ -1358,3 +1358,11 @@ x = X(a=1, b='s') [out] _testNamedTupleNew.py:12: error: Revealed type is 'Tuple[builtins.int, fallback=_testNamedTupleNew.Child]' + +[case testNewAnalyzerBasicTypeshed_newsemanal] +from typing import Dict, List, Tuple + +x: Dict[str, List[int]] +reveal_type(x['test'][0]) +[out] +_testNewAnalyzerBasicTypeshed_newsemanal.py:4: error: Revealed type is 'builtins.int*' From 6ebc4310df118bf992ddc3f96c1b5e6ff981a85c Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 7 Feb 2019 15:45:22 +0000 Subject: [PATCH 09/20] Postpone some test fixes --- mypy/build.py | 3 +-- mypy/checker.py | 4 ++-- mypy/test/hacks.py | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/mypy/build.py b/mypy/build.py index 05c30cae7f7a1..f240ef811aa82 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -1729,8 +1729,7 @@ def patch_dependency_parents(self) -> None: SCCs. """ for dep in self.dependencies: - if not self.options.new_semantic_analyzer: - self.manager.semantic_analyzer.add_submodules_to_parent_modules(dep, True) + self.manager.semantic_analyzer.add_submodules_to_parent_modules(dep, True) def fix_suppressed_dependencies(self, graph: Graph) -> None: """Corrects whether dependencies are considered stale in silent mode. diff --git a/mypy/checker.py b/mypy/checker.py index 7559186964f53..525726e42f05d 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1524,6 +1524,8 @@ def erase_override(t: Type) -> Type: def visit_class_def(self, defn: ClassDef) -> None: """Type check a class definition.""" typ = defn.info + if typ.is_protocol and typ.defn.type_vars: + self.check_protocol_variance(defn) for base in typ.mro[1:]: if base.is_final: self.fail(message_registry.CANNOT_INHERIT_FROM_FINAL.format(base.name()), defn) @@ -1560,8 +1562,6 @@ def visit_class_def(self, defn: ClassDef) -> None: callable_name=fullname) # TODO: Apply the sig to the actual TypeInfo so we can handle decorators # that completely swap out the type. (e.g. Callable[[Type[A]], Type[B]]) - if typ.is_protocol and typ.defn.type_vars: - self.check_protocol_variance(defn) def check_protocol_variance(self, defn: ClassDef) -> None: """Check that protocol definition is compatible with declared diff --git a/mypy/test/hacks.py b/mypy/test/hacks.py index 4674743ad3d07..7f7fa910c5de7 100644 --- a/mypy/test/hacks.py +++ b/mypy/test/hacks.py @@ -6,8 +6,8 @@ from typing_extensions import Final # Files to not run with new semantic analyzer. -new_semanal_blacklist = [] -['check-abstract.test', +new_semanal_blacklist = [ + 'check-abstract.test', 'check-async-await.test', 'check-attr.test', 'check-basic.test', From 9a3880994c05ed87677310acf2cd5677f3e4208f Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 7 Feb 2019 15:52:01 +0000 Subject: [PATCH 10/20] Cleaner solution for tuple --- mypy/newsemanal/typeanal.py | 8 +++----- mypy/semanal_shared.py | 4 ++++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/mypy/newsemanal/typeanal.py b/mypy/newsemanal/typeanal.py index a253a2a825afd..774bc1dc7589f 100644 --- a/mypy/newsemanal/typeanal.py +++ b/mypy/newsemanal/typeanal.py @@ -285,11 +285,9 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Opt " in a variable annotation", t) return AnyType(TypeOfAny.from_error) elif fullname == 'typing.Tuple': - # Tuple is special because it is involved in builtin import cycle. - try: - sym = self.api.lookup_fully_qualified('builtins.tuple') - except KeyError: - sym = None + # Tuple is special because it is involved in builtin import cycle + # and may be not ready when used. + sym = self.api.lookup_fully_qualified('builtins.tuple') if not sym or isinstance(sym.node, PlaceholderNode): self.api.record_incomplete_ref() return AnyType(TypeOfAny.special_form) diff --git a/mypy/semanal_shared.py b/mypy/semanal_shared.py index 4d8994e2084e4..6a8196d89a991 100644 --- a/mypy/semanal_shared.py +++ b/mypy/semanal_shared.py @@ -42,6 +42,10 @@ def lookup_qualified(self, name: str, ctx: Context, def lookup_fully_qualified(self, name: str) -> SymbolTableNode: raise NotImplementedError + @abstractmethod + def lookup_fully_qualified_or_none(self, name: str) -> Optional[SymbolTableNode]: + raise NotImplementedError + @abstractmethod def fail(self, msg: str, ctx: Context, serious: bool = False, *, blocker: bool = False) -> None: From 273d43f9f47b9d1e5e2e0f3d6c1d43f54aabfbca Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 7 Feb 2019 15:52:36 +0000 Subject: [PATCH 11/20] Cleaner solution for tuple --- mypy/newsemanal/typeanal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/newsemanal/typeanal.py b/mypy/newsemanal/typeanal.py index 774bc1dc7589f..19cf95971b83f 100644 --- a/mypy/newsemanal/typeanal.py +++ b/mypy/newsemanal/typeanal.py @@ -287,7 +287,7 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Opt elif fullname == 'typing.Tuple': # Tuple is special because it is involved in builtin import cycle # and may be not ready when used. - sym = self.api.lookup_fully_qualified('builtins.tuple') + sym = self.api.lookup_fully_qualified_or_none('builtins.tuple') if not sym or isinstance(sym.node, PlaceholderNode): self.api.record_incomplete_ref() return AnyType(TypeOfAny.special_form) From bfb6a3cd8167bfcd140f9bd5ad2449f2ac3ccb17 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 7 Feb 2019 16:03:48 +0000 Subject: [PATCH 12/20] More robust solution for builtin aliases --- mypy/newsemanal/semanal.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/mypy/newsemanal/semanal.py b/mypy/newsemanal/semanal.py index 91a6718c0da4b..9d5baef1d9032 100644 --- a/mypy/newsemanal/semanal.py +++ b/mypy/newsemanal/semanal.py @@ -348,6 +348,20 @@ def prepare_file(self, file_node: MypyFile) -> None: self.modules['builtins']) if file_node.fullname() == 'builtins': self.prepare_builtins_namespace(file_node) + if file_node.fullname() == 'typing': + self.prepare_typing_namespace(file_node) + + def prepare_typing_namespace(self, file_node: MypyFile) -> None: + """Remove dummy alias definitions. + + They will be replaced with real aliases when corresponding targets are ready. + """ + for stmt in file_node.defs.copy(): + if (isinstance(stmt, AssignmentStmt) and len(stmt.lvalues) == 1 and + isinstance(stmt.lvalues[0], NameExpr)): + # Assignment to a simple name, remove it if it is a dummy alias. + if 'typing.' + stmt.lvalues[0].name in type_aliases: + file_node.defs.remove(stmt) def prepare_builtins_namespace(self, file_node: MypyFile) -> None: names = file_node.names @@ -3770,10 +3784,8 @@ def add_builtin_aliases(self, tree: MypyFile) -> None: assert tree.fullname() == 'typing' for alias, target_name in type_aliases.items(): name = alias.split('.')[-1] - if name in tree.names and not isinstance(tree.names[name].node, (Var, PlaceholderNode)): + if name in tree.names and not isinstance(tree.names[name].node, PlaceholderNode): continue - if name in tree.names and isinstance(tree.names[name].node, Var): - del tree.names[name] tag = self.track_incomplete_refs() n = self.lookup_fully_qualified_or_none(target_name) if n: From f10744d116e12c9555c3b8b102dc14705c5b113b Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 7 Feb 2019 16:07:19 +0000 Subject: [PATCH 13/20] Better comments --- mypy/newsemanal/semanal_main.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mypy/newsemanal/semanal_main.py b/mypy/newsemanal/semanal_main.py index 7e462f77ea6f0..398869d4f974e 100644 --- a/mypy/newsemanal/semanal_main.py +++ b/mypy/newsemanal/semanal_main.py @@ -39,6 +39,7 @@ # Perform up to this many semantic analysis iterations until giving up trying to bind all names. MAX_ITERATIONS = 10 +# Number of passes over core modules before going on to the rest of the builtin SCC. CORE_WARMUP = 2 core_modules = ['typing', 'builtins', 'abc', 'collections'] @@ -68,7 +69,8 @@ def process_top_levels(graph: 'Graph', scc: List[str]) -> None: state.manager.incomplete_namespaces.update(scc) worklist = scc[:] - # HACK: process core stuff first. + # HACK: process core stuff first. This is mostly needed to support defining + # named tuples in builtin SCC. if all(m in worklist for m in core_modules): worklist += list(reversed(core_modules)) * CORE_WARMUP iteration = 0 From cf42267d9a773e052493cebf0d9dbbbae2e2618d Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 7 Feb 2019 16:32:45 +0000 Subject: [PATCH 14/20] Fix AnyStr --- mypy/newsemanal/semanal.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/mypy/newsemanal/semanal.py b/mypy/newsemanal/semanal.py index 9d5baef1d9032..b0d2986577051 100644 --- a/mypy/newsemanal/semanal.py +++ b/mypy/newsemanal/semanal.py @@ -2545,10 +2545,17 @@ def process_typevar_declaration(self, s: AssignmentStmt) -> None: assert node is not None assert node.fullname is not None node.kind = self.current_symbol_kind() - type_var = TypeVarExpr(name, node.fullname, values, upper_bound, variance) - type_var.line = call.line - call.analyzed = type_var - node.node = type_var + if isinstance(node.node, TypeVarExpr): + # Existing definition from previous semanal iteration, use it. + type_var = node.node + type_var.values = values + type_var.upper_bound = upper_bound + type_var.variance = variance + else: + type_var = TypeVarExpr(name, node.fullname, values, upper_bound, variance) + type_var.line = call.line + call.analyzed = type_var + node.node = type_var def check_typevar_name(self, call: CallExpr, name: str, context: Context) -> bool: name = unmangle(name) From 0ff8ee3279de6654abc3cef111c4c5e19fc4a444 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 7 Feb 2019 17:27:52 +0000 Subject: [PATCH 15/20] Fix aliases like A = Cls for generic classes --- mypy/newsemanal/typeanal.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mypy/newsemanal/typeanal.py b/mypy/newsemanal/typeanal.py index 19cf95971b83f..8db0677ec937a 100644 --- a/mypy/newsemanal/typeanal.py +++ b/mypy/newsemanal/typeanal.py @@ -356,7 +356,8 @@ def analyze_type_with_type_info(self, info: TypeInfo, args: List[Type], ctx: Con # Instance with an invalid number of type arguments. instance = Instance(info, self.anal_array(args), ctx.line, ctx.column) # Check type argument count. - if len(instance.args) != len(info.type_vars): + # TODO: remove this from here and replace with a proper separate pass. + if len(instance.args) != len(info.type_vars) and not self.defining_alias: fix_instance(instance, self.fail) if not args and self.options.disallow_any_generics and not self.defining_alias: # We report/patch invalid built-in instances already during second pass. From ef106ca235d2c94dddb411935f0c165a3832a6ca Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 7 Feb 2019 17:39:41 +0000 Subject: [PATCH 16/20] Fix test --- mypy/test/testpythoneval.py | 4 ++++ test-data/unit/pythoneval.test | 3 +++ 2 files changed, 7 insertions(+) diff --git a/mypy/test/testpythoneval.py b/mypy/test/testpythoneval.py index cef8a59c2510d..5633f261d9355 100644 --- a/mypy/test/testpythoneval.py +++ b/mypy/test/testpythoneval.py @@ -94,6 +94,10 @@ def test_python_evaluation(testcase: DataDrivenTestCase, cache_dir: str) -> None output.extend(interp_out) # Remove temp file. os.remove(program_path) + typeshed = os.path.sep + 'typeshed' + os.path.sep + for i, line in enumerate(output): + if typeshed in line: + output[i] = line.split(typeshed)[-1] assert_string_arrays_equal(adapt_output(testcase), output, 'Invalid output ({}, line {})'.format( testcase.file, testcase.line)) diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test index 3c7df911ab4e9..08bb84fa301a5 100644 --- a/test-data/unit/pythoneval.test +++ b/test-data/unit/pythoneval.test @@ -1365,4 +1365,7 @@ from typing import Dict, List, Tuple x: Dict[str, List[int]] reveal_type(x['test'][0]) [out] +stdlib/3/typing.pyi: error: Class typing.Sequence has abstract attributes "__len__" +stdlib/3/typing.pyi: note: If it is meant to be abstract, add 'abc.ABCMeta' as an explicit metaclass +stdlib/2and3/builtins.pyi:39: error: Name '__class__' already defined on line 39 _testNewAnalyzerBasicTypeshed_newsemanal.py:4: error: Revealed type is 'builtins.int*' From e43d946190919f2af263e649496869845198dff4 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 7 Feb 2019 17:52:06 +0000 Subject: [PATCH 17/20] Fix self check --- mypy/newsemanal/semanal_shared.py | 4 ++++ mypy/semanal_shared.py | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mypy/newsemanal/semanal_shared.py b/mypy/newsemanal/semanal_shared.py index 6b2d56785e079..407792c17b005 100644 --- a/mypy/newsemanal/semanal_shared.py +++ b/mypy/newsemanal/semanal_shared.py @@ -42,6 +42,10 @@ def lookup_qualified(self, name: str, ctx: Context, def lookup_fully_qualified(self, name: str) -> SymbolTableNode: raise NotImplementedError + @abstractmethod + def lookup_fully_qualified_or_none(self, name: str) -> Optional[SymbolTableNode]: + raise NotImplementedError + @abstractmethod def fail(self, msg: str, ctx: Context, serious: bool = False, *, blocker: bool = False) -> None: diff --git a/mypy/semanal_shared.py b/mypy/semanal_shared.py index 6a8196d89a991..4d8994e2084e4 100644 --- a/mypy/semanal_shared.py +++ b/mypy/semanal_shared.py @@ -42,10 +42,6 @@ def lookup_qualified(self, name: str, ctx: Context, def lookup_fully_qualified(self, name: str) -> SymbolTableNode: raise NotImplementedError - @abstractmethod - def lookup_fully_qualified_or_none(self, name: str) -> Optional[SymbolTableNode]: - raise NotImplementedError - @abstractmethod def fail(self, msg: str, ctx: Context, serious: bool = False, *, blocker: bool = False) -> None: From 19a4352f48fa8a5f10ffd5bfec04e4623b8e9694 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 7 Feb 2019 19:19:07 +0000 Subject: [PATCH 18/20] Fix Windows --- mypy/test/testpythoneval.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mypy/test/testpythoneval.py b/mypy/test/testpythoneval.py index 5633f261d9355..f4356ae827035 100644 --- a/mypy/test/testpythoneval.py +++ b/mypy/test/testpythoneval.py @@ -23,7 +23,7 @@ from mypy.defaults import PYTHON3_VERSION from mypy.test.config import test_temp_dir from mypy.test.data import DataDrivenTestCase, DataSuite -from mypy.test.helpers import assert_string_arrays_equal, run_command +from mypy.test.helpers import assert_string_arrays_equal, run_command, normalize_error_messages from mypy.util import try_find_python2_interpreter from mypy import api @@ -94,10 +94,10 @@ def test_python_evaluation(testcase: DataDrivenTestCase, cache_dir: str) -> None output.extend(interp_out) # Remove temp file. os.remove(program_path) - typeshed = os.path.sep + 'typeshed' + os.path.sep + output = normalize_error_messages(output) for i, line in enumerate(output): - if typeshed in line: - output[i] = line.split(typeshed)[-1] + if '/typeshed/' in line: + output[i] = line.split('/typeshed/')[-1] assert_string_arrays_equal(adapt_output(testcase), output, 'Invalid output ({}, line {})'.format( testcase.file, testcase.line)) From 9d4bbdf21ef0bb95a5d4dfa3e121a21484be3dfe Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 7 Feb 2019 21:11:16 +0000 Subject: [PATCH 19/20] Fix Windows for real --- mypy/test/testpythoneval.py | 7 +++---- test-data/unit/pythoneval.test | 6 +++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/mypy/test/testpythoneval.py b/mypy/test/testpythoneval.py index f4356ae827035..bd507e630e8b2 100644 --- a/mypy/test/testpythoneval.py +++ b/mypy/test/testpythoneval.py @@ -23,7 +23,7 @@ from mypy.defaults import PYTHON3_VERSION from mypy.test.config import test_temp_dir from mypy.test.data import DataDrivenTestCase, DataSuite -from mypy.test.helpers import assert_string_arrays_equal, run_command, normalize_error_messages +from mypy.test.helpers import assert_string_arrays_equal, run_command from mypy.util import try_find_python2_interpreter from mypy import api @@ -94,10 +94,9 @@ def test_python_evaluation(testcase: DataDrivenTestCase, cache_dir: str) -> None output.extend(interp_out) # Remove temp file. os.remove(program_path) - output = normalize_error_messages(output) for i, line in enumerate(output): - if '/typeshed/' in line: - output[i] = line.split('/typeshed/')[-1] + if os.path.sep + 'typeshed' + os.path.sep in line: + output[i] = line.split(os.path.sep)[-1] assert_string_arrays_equal(adapt_output(testcase), output, 'Invalid output ({}, line {})'.format( testcase.file, testcase.line)) diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test index 08bb84fa301a5..9eba163b073b0 100644 --- a/test-data/unit/pythoneval.test +++ b/test-data/unit/pythoneval.test @@ -1365,7 +1365,7 @@ from typing import Dict, List, Tuple x: Dict[str, List[int]] reveal_type(x['test'][0]) [out] -stdlib/3/typing.pyi: error: Class typing.Sequence has abstract attributes "__len__" -stdlib/3/typing.pyi: note: If it is meant to be abstract, add 'abc.ABCMeta' as an explicit metaclass -stdlib/2and3/builtins.pyi:39: error: Name '__class__' already defined on line 39 +typing.pyi: error: Class typing.Sequence has abstract attributes "__len__" +typing.pyi: note: If it is meant to be abstract, add 'abc.ABCMeta' as an explicit metaclass +builtins.pyi:39: error: Name '__class__' already defined on line 39 _testNewAnalyzerBasicTypeshed_newsemanal.py:4: error: Revealed type is 'builtins.int*' From f485a97281b5160080c7903c39575ad76842239a Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 12 Feb 2019 15:12:16 +0000 Subject: [PATCH 20/20] Address CR --- mypy/newsemanal/semanal.py | 2 +- mypy/newsemanal/semanal_shared.py | 5 +++++ mypy/newsemanal/typeanal.py | 5 ++++- test-data/unit/check-newsemanal.test | 6 ++++++ test-data/unit/fixtures/complex.pyi | 1 + 5 files changed, 17 insertions(+), 2 deletions(-) diff --git a/mypy/newsemanal/semanal.py b/mypy/newsemanal/semanal.py index b0d2986577051..e6023a5e32d65 100644 --- a/mypy/newsemanal/semanal.py +++ b/mypy/newsemanal/semanal.py @@ -352,7 +352,7 @@ def prepare_file(self, file_node: MypyFile) -> None: self.prepare_typing_namespace(file_node) def prepare_typing_namespace(self, file_node: MypyFile) -> None: - """Remove dummy alias definitions. + """Remove dummy alias definitions such as List = TypeAlias(object) from typing. They will be replaced with real aliases when corresponding targets are ready. """ diff --git a/mypy/newsemanal/semanal_shared.py b/mypy/newsemanal/semanal_shared.py index 407792c17b005..39279eee998af 100644 --- a/mypy/newsemanal/semanal_shared.py +++ b/mypy/newsemanal/semanal_shared.py @@ -68,6 +68,11 @@ def record_incomplete_ref(self) -> None: def defer(self) -> None: raise NotImplementedError + @abstractmethod + def is_incomplete_namespace(self, fullname: str) -> bool: + """Is a module or class namespace potentially missing some definitions?""" + raise NotImplementedError + @trait class SemanticAnalyzerInterface(SemanticAnalyzerCoreInterface): diff --git a/mypy/newsemanal/typeanal.py b/mypy/newsemanal/typeanal.py index 8db0677ec937a..c64406265a726 100644 --- a/mypy/newsemanal/typeanal.py +++ b/mypy/newsemanal/typeanal.py @@ -289,7 +289,10 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Opt # and may be not ready when used. sym = self.api.lookup_fully_qualified_or_none('builtins.tuple') if not sym or isinstance(sym.node, PlaceholderNode): - self.api.record_incomplete_ref() + if self.api.is_incomplete_namespace('builtins'): + self.api.record_incomplete_ref() + else: + self.fail("Name 'tuple' is not defined", t) return AnyType(TypeOfAny.special_form) if len(t.args) == 0 and not t.empty_tuple_index: # Bare 'Tuple' is same as 'tuple' diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test index ae3fb3d0305bc..aa32af3801ff2 100644 --- a/test-data/unit/check-newsemanal.test +++ b/test-data/unit/check-newsemanal.test @@ -899,6 +899,12 @@ class C: pass import a from b import C +[case testNewAnalyzerIncompleteFixture] +from typing import Tuple + +x: Tuple[int] # E: Name 'tuple' is not defined +[builtins fixtures/complex.pyi] + [case testNewAnalyzerMetaclass1] class A(metaclass=B): pass diff --git a/test-data/unit/fixtures/complex.pyi b/test-data/unit/fixtures/complex.pyi index d4135be67462f..bcd03a2562e5f 100644 --- a/test-data/unit/fixtures/complex.pyi +++ b/test-data/unit/fixtures/complex.pyi @@ -1,4 +1,5 @@ # Builtins stub used for some float/complex test cases. +# Please don't add tuple to this file, it is used to test incomplete fixtures. class object: def __init__(self): pass