Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fine-grained: Apply semantic analyzer patch callbacks #4658

Merged
merged 2 commits into from
Mar 2, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions mypy/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@

from mypy.nodes import (MODULE_REF, MypyFile, Node, ImportBase, Import, ImportFrom, ImportAll)
from mypy.semanal_pass1 import SemanticAnalyzerPass1
from mypy.semanal import SemanticAnalyzerPass2
from mypy.semanal import SemanticAnalyzerPass2, apply_semantic_analyzer_patches
from mypy.semanal_pass3 import SemanticAnalyzerPass3
from mypy.checker import TypeChecker
from mypy.indirection import TypeIndirectionVisitor
Expand Down Expand Up @@ -1958,9 +1958,7 @@ def semantic_analysis_pass_three(self) -> None:
self.patches = patches + self.patches

def semantic_analysis_apply_patches(self) -> None:
patches_by_priority = sorted(self.patches, key=lambda x: x[0])
for priority, patch_func in patches_by_priority:
patch_func()
apply_semantic_analyzer_patches(self.patches)

def type_check_first_pass(self) -> None:
if self.options.semantic_analysis_only:
Expand Down
18 changes: 14 additions & 4 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,24 +309,24 @@ def visit_file(self, file_node: MypyFile, fnam: str, options: Options,
del self.cur_mod_node
del self.globals

def refresh_partial(self, node: Union[MypyFile, FuncItem, OverloadedFuncDef]) -> None:
def refresh_partial(self, node: Union[MypyFile, FuncItem, OverloadedFuncDef],
patches: List[Tuple[int, Callable[[], None]]]) -> None:
"""Refresh a stale target in fine-grained incremental mode."""
self.patches = patches
self.scope.enter_file(self.cur_mod_id)
if isinstance(node, MypyFile):
self.refresh_top_level(node)
else:
self.recurse_into_functions = True
self.accept(node)
self.scope.leave()
del self.patches

def refresh_top_level(self, file_node: MypyFile) -> None:
"""Reanalyze a stale module top-level in fine-grained incremental mode."""
# TODO: Invoke patches in fine-grained incremental mode.
self.patches = []
self.recurse_into_functions = False
for d in file_node.defs:
self.accept(d)
del self.patches

@contextmanager
def file_context(self, file_node: MypyFile, fnam: str, options: Options,
Expand Down Expand Up @@ -4307,3 +4307,13 @@ def visit_any(self, t: AnyType) -> Type:
if t.type_of_any == TypeOfAny.explicit:
return t.copy_modified(TypeOfAny.special_form)
return t


def apply_semantic_analyzer_patches(patches: List[Tuple[int, Callable[[], None]]]) -> None:
"""Call patch callbacks in the right order.

This should happen after semantic analyzer pass 3.
"""
patches_by_priority = sorted(patches, key=lambda x: x[0])
for priority, patch_func in patches_by_priority:
patch_func()
5 changes: 4 additions & 1 deletion mypy/semanal_pass3.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,10 @@ def visit_file(self, file_node: MypyFile, fnam: str, options: Options,
del self.cur_mod_node
self.patches = []

def refresh_partial(self, node: Union[MypyFile, FuncItem, OverloadedFuncDef]) -> None:
def refresh_partial(self, node: Union[MypyFile, FuncItem, OverloadedFuncDef],
patches: List[Tuple[int, Callable[[], None]]]) -> None:
"""Refresh a stale target in fine-grained incremental mode."""
self.patches = patches
self.scope.enter_file(self.sem.cur_mod_id)
if isinstance(node, MypyFile):
self.recurse_into_functions = False
Expand All @@ -81,6 +83,7 @@ def refresh_partial(self, node: Union[MypyFile, FuncItem, OverloadedFuncDef]) ->
self.recurse_into_functions = True
self.accept(node)
self.scope.leave()
self.patches = []

def refresh_top_level(self, file_node: MypyFile) -> None:
"""Reanalyze a stale module top-level in fine-grained incremental mode."""
Expand Down
22 changes: 13 additions & 9 deletions mypy/server/aststrip.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,20 +78,24 @@ def strip_file_top_level(self, file_node: MypyFile) -> None:

def visit_class_def(self, node: ClassDef) -> None:
"""Strip class body and type info, but don't strip methods."""
node.info.type_vars = []
node.info.bases = []
node.info.abstract_attributes = []
node.info.mro = []
node.info.add_type_vars()
node.info.tuple_type = None
node.info.typeddict_type = None
node.info._cache = set()
node.info._cache_proper = set()
self.strip_type_info(node.info)
node.base_type_exprs.extend(node.removed_base_type_exprs)
node.removed_base_type_exprs = []
with self.enter_class(node.info):
super().visit_class_def(node)

def strip_type_info(self, info: TypeInfo) -> None:
info.type_vars = []
info.bases = []
info.abstract_attributes = []
info.mro = []
info.add_type_vars()
info.tuple_type = None
info.typeddict_type = None
info.tuple_type = None
info._cache = set()
info._cache_proper = set()

def visit_func_def(self, node: FuncDef) -> None:
if not self.recurse_into_functions:
return
Expand Down
13 changes: 10 additions & 3 deletions mypy/server/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,9 @@
"""

import os.path
from typing import Dict, List, Set, Tuple, Iterable, Union, Optional, Mapping, NamedTuple
from typing import (
Dict, List, Set, Tuple, Iterable, Union, Optional, Mapping, NamedTuple, Callable
)

from mypy.build import (
BuildManager, State, BuildSource, Graph, load_graph, SavedCache, CacheMeta,
Expand All @@ -133,6 +135,7 @@
)
from mypy.options import Options
from mypy.types import Type
from mypy.semanal import apply_semantic_analyzer_patches
from mypy.server.astdiff import (
snapshot_symbol_table, compare_symbol_table_snapshots, is_identical_type, SnapshotItem
)
Expand Down Expand Up @@ -837,6 +840,8 @@ def key(node: DeferredNode) -> int:
strip_target(deferred.node)
semantic_analyzer = manager.semantic_analyzer

patches = [] # type: List[Tuple[int, Callable[[], None]]]

# Second pass of semantic analysis. We don't redo the first pass, because it only
# does local things that won't go stale.
for deferred in nodes:
Expand All @@ -845,7 +850,7 @@ def key(node: DeferredNode) -> int:
fnam=file_node.path,
options=manager.options,
active_type=deferred.active_typeinfo):
manager.semantic_analyzer.refresh_partial(deferred.node)
manager.semantic_analyzer.refresh_partial(deferred.node, patches)

# Third pass of semantic analysis.
for deferred in nodes:
Expand All @@ -854,7 +859,9 @@ def key(node: DeferredNode) -> int:
fnam=file_node.path,
options=manager.options,
active_type=deferred.active_typeinfo):
manager.semantic_analyzer_pass3.refresh_partial(deferred.node)
manager.semantic_analyzer_pass3.refresh_partial(deferred.node, patches)

apply_semantic_analyzer_patches(patches)

# Merge symbol tables to preserve identities of AST nodes. The file node will remain
# the same, but other nodes may have been recreated with different identities, such as
Expand Down
34 changes: 34 additions & 0 deletions test-data/unit/fine-grained.test
Original file line number Diff line number Diff line change
Expand Up @@ -2475,3 +2475,37 @@ else:
[builtins fixtures/ops.pyi]
[out]
==

[case testNamedTupleWithinFunction]
from typing import NamedTuple
import b
def f() -> None:
b.x
n = NamedTuple('n', [])
[file b.py]
x = 0
[file b.py.2]
x = ''
[out]
==

[case testNamedTupleFallback]
# This test will fail without semantic analyzer pass 2 patches
import a
[file a.py]
import b
[file b.py]
from typing import NamedTuple
import c
c.x
class N(NamedTuple):
count: int
[file c.py]
x = 0
[file c.py.2]
x = ''
[builtins fixtures/tuple.pyi]
[out]
b.py:5: error: Incompatible types in assignment (expression has type "int", base class "tuple" defined the type as "Callable[[Tuple[int, ...], Any], int]")
==
b.py:5: error: Incompatible types in assignment (expression has type "int", base class "tuple" defined the type as "Callable[[Tuple[int, ...], Any], int]")