Skip to content

Commit 61df970

Browse files
committed
Fine-grained: apply semantic analyzer patch callbacks
This fixes crashes and other issues. Fixes #4657.
1 parent 7264ae5 commit 61df970

5 files changed

Lines changed: 72 additions & 19 deletions

File tree

‎mypy/build.py‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535

3636
from mypy.nodes import (MODULE_REF, MypyFile, Node, ImportBase, Import, ImportFrom, ImportAll)
3737
from mypy.semanal_pass1 import SemanticAnalyzerPass1
38-
from mypy.semanal import SemanticAnalyzerPass2
38+
from mypy.semanal import SemanticAnalyzerPass2, apply_semantic_analyzer_patches
3939
from mypy.semanal_pass3 import SemanticAnalyzerPass3
4040
from mypy.checker import TypeChecker
4141
from mypy.indirection import TypeIndirectionVisitor
@@ -1958,9 +1958,7 @@ def semantic_analysis_pass_three(self) -> None:
19581958
self.patches = patches + self.patches
19591959

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

19651963
def type_check_first_pass(self) -> None:
19661964
if self.options.semantic_analysis_only:

‎mypy/semanal.py‎

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -309,24 +309,24 @@ def visit_file(self, file_node: MypyFile, fnam: str, options: Options,
309309
del self.cur_mod_node
310310
del self.globals
311311

312-
def refresh_partial(self, node: Union[MypyFile, FuncItem, OverloadedFuncDef]) -> None:
312+
def refresh_partial(self, node: Union[MypyFile, FuncItem, OverloadedFuncDef],
313+
patches: List[Tuple[int, Callable[[], None]]]) -> None:
313314
"""Refresh a stale target in fine-grained incremental mode."""
315+
self.patches = patches
314316
self.scope.enter_file(self.cur_mod_id)
315317
if isinstance(node, MypyFile):
316318
self.refresh_top_level(node)
317319
else:
318320
self.recurse_into_functions = True
319321
self.accept(node)
320322
self.scope.leave()
323+
del self.patches
321324

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

331331
@contextmanager
332332
def file_context(self, file_node: MypyFile, fnam: str, options: Options,
@@ -4307,3 +4307,13 @@ def visit_any(self, t: AnyType) -> Type:
43074307
if t.type_of_any == TypeOfAny.explicit:
43084308
return t.copy_modified(TypeOfAny.special_form)
43094309
return t
4310+
4311+
4312+
def apply_semantic_analyzer_patches(patches: List[Tuple[int, Callable[[], None]]]) -> None:
4313+
"""Call patch callbacks in the right order.
4314+
4315+
This should happen after semantic analyzer pass 3.
4316+
"""
4317+
patches_by_priority = sorted(patches, key=lambda x: x[0])
4318+
for priority, patch_func in patches_by_priority:
4319+
patch_func()

‎mypy/server/aststrip.py‎

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -78,20 +78,24 @@ def strip_file_top_level(self, file_node: MypyFile) -> None:
7878

7979
def visit_class_def(self, node: ClassDef) -> None:
8080
"""Strip class body and type info, but don't strip methods."""
81-
node.info.type_vars = []
82-
node.info.bases = []
83-
node.info.abstract_attributes = []
84-
node.info.mro = []
85-
node.info.add_type_vars()
86-
node.info.tuple_type = None
87-
node.info.typeddict_type = None
88-
node.info._cache = set()
89-
node.info._cache_proper = set()
81+
self.strip_type_info(node.info)
9082
node.base_type_exprs.extend(node.removed_base_type_exprs)
9183
node.removed_base_type_exprs = []
9284
with self.enter_class(node.info):
9385
super().visit_class_def(node)
9486

87+
def strip_type_info(self, info: TypeInfo) -> None:
88+
info.type_vars = []
89+
info.bases = []
90+
info.abstract_attributes = []
91+
info.mro = []
92+
info.add_type_vars()
93+
info.tuple_type = None
94+
info.typeddict_type = None
95+
info.tuple_type = None
96+
info._cache = set()
97+
info._cache_proper = set()
98+
9599
def visit_func_def(self, node: FuncDef) -> None:
96100
if not self.recurse_into_functions:
97101
return

‎mypy/server/update.py‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,9 @@
119119
"""
120120

121121
import os.path
122-
from typing import Dict, List, Set, Tuple, Iterable, Union, Optional, Mapping, NamedTuple
122+
from typing import (
123+
Dict, List, Set, Tuple, Iterable, Union, Optional, Mapping, NamedTuple, Callable
124+
)
123125

124126
from mypy.build import (
125127
BuildManager, State, BuildSource, Graph, load_graph, SavedCache, CacheMeta,
@@ -133,6 +135,7 @@
133135
)
134136
from mypy.options import Options
135137
from mypy.types import Type
138+
from mypy.semanal import apply_semantic_analyzer_patches
136139
from mypy.server.astdiff import (
137140
snapshot_symbol_table, compare_symbol_table_snapshots, is_identical_type, SnapshotItem
138141
)
@@ -837,6 +840,8 @@ def key(node: DeferredNode) -> int:
837840
strip_target(deferred.node)
838841
semantic_analyzer = manager.semantic_analyzer
839842

843+
patches = [] # type: List[Tuple[int, Callable[[], None]]]
844+
840845
# Second pass of semantic analysis. We don't redo the first pass, because it only
841846
# does local things that won't go stale.
842847
for deferred in nodes:
@@ -845,7 +850,7 @@ def key(node: DeferredNode) -> int:
845850
fnam=file_node.path,
846851
options=manager.options,
847852
active_type=deferred.active_typeinfo):
848-
manager.semantic_analyzer.refresh_partial(deferred.node)
853+
manager.semantic_analyzer.refresh_partial(deferred.node, patches)
849854

850855
# Third pass of semantic analysis.
851856
for deferred in nodes:
@@ -856,6 +861,8 @@ def key(node: DeferredNode) -> int:
856861
active_type=deferred.active_typeinfo):
857862
manager.semantic_analyzer_pass3.refresh_partial(deferred.node)
858863

864+
apply_semantic_analyzer_patches(patches)
865+
859866
# Merge symbol tables to preserve identities of AST nodes. The file node will remain
860867
# the same, but other nodes may have been recreated with different identities, such as
861868
# NamedTuples defined using assignment statements.

‎test-data/unit/fine-grained.test‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2475,3 +2475,37 @@ else:
24752475
[builtins fixtures/ops.pyi]
24762476
[out]
24772477
==
2478+
2479+
[case testNamedTupleWithinFunction]
2480+
from typing import NamedTuple
2481+
import b
2482+
def f() -> None:
2483+
b.x
2484+
n = NamedTuple('n', [])
2485+
[file b.py]
2486+
x = 0
2487+
[file b.py.2]
2488+
x = ''
2489+
[out]
2490+
==
2491+
2492+
[case testNamedTupleFallback]
2493+
# This test will fail without semantic analyzer pass 2 patches
2494+
import a
2495+
[file a.py]
2496+
import b
2497+
[file b.py]
2498+
from typing import NamedTuple
2499+
import c
2500+
c.x
2501+
class N(NamedTuple):
2502+
count: int
2503+
[file c.py]
2504+
x = 0
2505+
[file c.py.2]
2506+
x = ''
2507+
[builtins fixtures/tuple.pyi]
2508+
[out]
2509+
b.py:5: error: Incompatible types in assignment (expression has type "int", base class "tuple" defined the type as "Callable[[Tuple[int, ...], Any], int]")
2510+
==
2511+
b.py:5: error: Incompatible types in assignment (expression has type "int", base class "tuple" defined the type as "Callable[[Tuple[int, ...], Any], int]")

0 commit comments

Comments
 (0)