Skip to content

Commit 7264ae5

Browse files
authored
Fine-grained: Fix AST merge issues (#4652)
This fixes all but one AST merge checker test failures. The remaining one (testFineGrainedCallable) seems tricky to fix so I'll leave it for later. I decided not to add tests for things that can be caught using the merge checker. We'll have to run the merge checker periodically since it's not enabled by default in the test suite.
1 parent 8a4f85e commit 7264ae5

12 files changed

+154
-62
lines changed

mypy/build.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1992,7 +1992,9 @@ def finish_passes(self) -> None:
19921992
return
19931993
with self.wrap_context():
19941994
# Some tests want to look at the set of all types.
1995-
if manager.options.use_builtins_fixtures or manager.options.dump_deps:
1995+
options = manager.options
1996+
if ((options.use_builtins_fixtures and not options.fine_grained_incremental) or
1997+
manager.options.dump_deps):
19961998
manager.all_types.update(self.type_map())
19971999

19982000
if self.options.incremental:

mypy/nodes.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2206,11 +2206,18 @@ def type_str(typ: 'mypy.types.Type') -> str:
22062206
if isinstance(node, Var) and node.type:
22072207
description += ' ({})'.format(type_str(node.type))
22082208
names.append(description)
2209+
items = [
2210+
'Name({})'.format(self.fullname()),
2211+
base,
2212+
mro,
2213+
('Names', names),
2214+
]
2215+
if self.declared_metaclass:
2216+
items.append('DeclaredMetaclass({})'.format(type_str(self.declared_metaclass)))
2217+
if self.metaclass_type:
2218+
items.append('MetaclassType({})'.format(type_str(self.metaclass_type)))
22092219
return mypy.strconv.dump_tagged(
2210-
['Name({})'.format(self.fullname()),
2211-
base,
2212-
mro,
2213-
('Names', names)],
2220+
items,
22142221
head,
22152222
str_conv=str_conv)
22162223

mypy/semanal.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,8 @@ def visit_file(self, file_node: MypyFile, fnam: str, options: Options,
306306

307307
del self.options
308308
del self.patches
309+
del self.cur_mod_node
310+
del self.globals
309311

310312
def refresh_partial(self, node: Union[MypyFile, FuncItem, OverloadedFuncDef]) -> None:
311313
"""Refresh a stale target in fine-grained incremental mode."""
@@ -324,6 +326,7 @@ def refresh_top_level(self, file_node: MypyFile) -> None:
324326
self.recurse_into_functions = False
325327
for d in file_node.defs:
326328
self.accept(d)
329+
del self.patches
327330

328331
@contextmanager
329332
def file_context(self, file_node: MypyFile, fnam: str, options: Options,

mypy/semanal_pass3.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ def visit_file(self, file_node: MypyFile, fnam: str, options: Options,
6868
self.scope.enter_file(file_node.fullname())
6969
self.accept(file_node)
7070
self.scope.leave()
71+
del self.cur_mod_node
72+
self.patches = []
7173

7274
def refresh_partial(self, node: Union[MypyFile, FuncItem, OverloadedFuncDef]) -> None:
7375
"""Refresh a stale target in fine-grained incremental mode."""

mypy/server/astmerge.py

Lines changed: 74 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,14 @@
5050
from mypy.nodes import (
5151
Node, MypyFile, SymbolTable, Block, AssignmentStmt, NameExpr, MemberExpr, RefExpr, TypeInfo,
5252
FuncDef, ClassDef, NamedTupleExpr, SymbolNode, Var, Statement, SuperExpr, NewTypeExpr,
53-
OverloadedFuncDef, LambdaExpr, TypedDictExpr, EnumCallExpr, MDEF
53+
OverloadedFuncDef, LambdaExpr, TypedDictExpr, EnumCallExpr, FuncBase, TypeAliasExpr, CallExpr,
54+
MDEF
5455
)
5556
from mypy.traverser import TraverserVisitor
5657
from mypy.types import (
5758
Type, TypeVisitor, Instance, AnyType, NoneTyp, CallableType, DeletedType, PartialType,
5859
TupleType, TypeType, TypeVarType, TypedDictType, UnboundType, UninhabitedType, UnionType,
59-
Overloaded, TypeVarDef
60+
Overloaded, TypeVarDef, TypeList
6061
)
6162
from mypy.util import get_prefix
6263

@@ -156,34 +157,41 @@ def visit_block(self, node: Block) -> None:
156157

157158
def visit_func_def(self, node: FuncDef) -> None:
158159
node = self.fixup(node)
159-
if node.type:
160-
self.fixup_type(node.type)
161-
if node.info:
162-
node.info = self.fixup(node.info)
160+
self.process_base_func(node)
163161
super().visit_func_def(node)
164162

165163
def visit_overloaded_func_def(self, node: OverloadedFuncDef) -> None:
166-
if node.info:
167-
node.info = self.fixup(node.info)
164+
self.process_base_func(node)
168165
super().visit_overloaded_func_def(node)
169166

170167
def visit_class_def(self, node: ClassDef) -> None:
171168
# TODO additional things?
172169
node.defs.body = self.replace_statements(node.defs.body)
173170
node.info = self.fixup(node.info)
171+
info = node.info
174172
for tv in node.type_vars:
175173
self.process_type_var_def(tv)
176-
self.process_type_info(node.info)
174+
if info:
175+
if info.is_named_tuple:
176+
self.process_synthetic_type_info(info)
177+
else:
178+
self.process_type_info(info)
177179
super().visit_class_def(node)
178180

181+
def process_base_func(self, node: FuncBase) -> None:
182+
self.fixup_type(node.type)
183+
node.info = self.fixup(node.info)
184+
if node.unanalyzed_type:
185+
# Unanalyzed types can have AST node references
186+
self.fixup_type(node.unanalyzed_type)
187+
179188
def process_type_var_def(self, tv: TypeVarDef) -> None:
180189
for value in tv.values:
181190
self.fixup_type(value)
182191
self.fixup_type(tv.upper_bound)
183192

184193
def visit_assignment_stmt(self, node: AssignmentStmt) -> None:
185-
if node.type:
186-
self.fixup_type(node.type)
194+
self.fixup_type(node.type)
187195
super().visit_assignment_stmt(node)
188196

189197
# Expressions
@@ -200,46 +208,56 @@ def visit_member_expr(self, node: MemberExpr) -> None:
200208
def visit_ref_expr(self, node: RefExpr) -> None:
201209
if node.node is not None:
202210
node.node = self.fixup(node.node)
211+
if isinstance(node.node, Var):
212+
# The Var node may be an orphan and won't otherwise be processed.
213+
fixup_var(node.node, self.replacements)
203214

204215
def visit_namedtuple_expr(self, node: NamedTupleExpr) -> None:
205216
super().visit_namedtuple_expr(node)
206217
node.info = self.fixup(node.info)
207-
self.process_type_info(node.info)
218+
self.process_synthetic_type_info(node.info)
208219

209220
def visit_super_expr(self, node: SuperExpr) -> None:
210221
super().visit_super_expr(node)
211222
if node.info is not None:
212223
node.info = self.fixup(node.info)
213224

225+
def visit_call_expr(self, node: CallExpr) -> None:
226+
super().visit_call_expr(node)
227+
if isinstance(node.analyzed, SymbolNode):
228+
node.analyzed = self.fixup(node.analyzed)
229+
214230
def visit_newtype_expr(self, node: NewTypeExpr) -> None:
215231
if node.info:
216232
node.info = self.fixup(node.info)
217-
self.process_type_info(node.info)
218-
if node.old_type:
219-
self.fixup_type(node.old_type)
233+
self.process_synthetic_type_info(node.info)
234+
self.fixup_type(node.old_type)
220235
super().visit_newtype_expr(node)
221236

222237
def visit_lambda_expr(self, node: LambdaExpr) -> None:
223-
if node.info:
224-
node.info = self.fixup(node.info)
238+
node.info = self.fixup(node.info)
225239
super().visit_lambda_expr(node)
226240

227241
def visit_typeddict_expr(self, node: TypedDictExpr) -> None:
228-
node.info = self.fixup(node.info)
229242
super().visit_typeddict_expr(node)
243+
node.info = self.fixup(node.info)
244+
self.process_synthetic_type_info(node.info)
230245

231246
def visit_enum_call_expr(self, node: EnumCallExpr) -> None:
232247
node.info = self.fixup(node.info)
233-
self.process_type_info(node.info)
248+
self.process_synthetic_type_info(node.info)
234249
super().visit_enum_call_expr(node)
235250

251+
def visit_type_alias_expr(self, node: TypeAliasExpr) -> None:
252+
self.fixup_type(node.type)
253+
self.fixup_type(node.fallback)
254+
super().visit_type_alias_expr(node)
255+
236256
# Others
237257

238258
def visit_var(self, node: Var) -> None:
239-
if node.info:
240-
node.info = self.fixup(node.info)
241-
if node.type:
242-
self.fixup_type(node.type)
259+
node.info = self.fixup(node.info)
260+
self.fixup_type(node.type)
243261
super().visit_var(node)
244262

245263
# Helpers
@@ -251,25 +269,34 @@ def fixup(self, node: SN) -> SN:
251269
return cast(SN, new)
252270
return node
253271

254-
def fixup_type(self, typ: Type) -> None:
255-
typ.accept(TypeReplaceVisitor(self.replacements))
272+
def fixup_type(self, typ: Optional[Type]) -> None:
273+
if typ is not None:
274+
typ.accept(TypeReplaceVisitor(self.replacements))
256275

257276
def process_type_info(self, info: Optional[TypeInfo]) -> None:
258277
if info is None:
259278
return
260-
# TODO: Additional things:
261-
# - declared_metaclass
262-
# - metaclass_type
263-
# - _promote
264-
# - typeddict_type
265-
# - replaced
279+
self.fixup_type(info.declared_metaclass)
280+
self.fixup_type(info.metaclass_type)
281+
self.fixup_type(info._promote)
282+
self.fixup_type(info.tuple_type)
283+
self.fixup_type(info.typeddict_type)
284+
info.defn.info = self.fixup(info)
285+
info.replaced = self.fixup(info.replaced)
266286
replace_nodes_in_symbol_table(info.names, self.replacements)
267287
for i, item in enumerate(info.mro):
268288
info.mro[i] = self.fixup(info.mro[i])
269289
for i, base in enumerate(info.bases):
270290
self.fixup_type(info.bases[i])
271-
if info.tuple_type:
272-
self.fixup_type(info.tuple_type)
291+
292+
def process_synthetic_type_info(self, info: TypeInfo) -> None:
293+
# Synthetic types (types not created using a class statement) don't
294+
# have bodies in the AST so we need to iterate over their symbol
295+
# tables separately, unlike normal classes.
296+
self.process_type_info(info)
297+
for name, node in info.names.items():
298+
if node.node:
299+
node.node.accept(self)
273300

274301
def replace_statements(self, nodes: List[Statement]) -> List[Statement]:
275302
result = []
@@ -345,6 +372,10 @@ def visit_unbound_type(self, typ: UnboundType) -> None:
345372
for arg in typ.args:
346373
arg.accept(self)
347374

375+
def visit_type_list(self, typ: TypeList) -> None:
376+
for item in typ.items:
377+
item.accept(self)
378+
348379
def visit_uninhabited_type(self, typ: UninhabitedType) -> None:
349380
pass
350381

@@ -371,15 +402,16 @@ def replace_nodes_in_symbol_table(symbols: SymbolTable,
371402
new = replacements[node.node]
372403
new.__dict__ = node.node.__dict__
373404
node.node = new
374-
# TODO: Other node types
375-
if isinstance(node.node, Var) and node.node.type:
376-
node.node.type.accept(TypeReplaceVisitor(replacements))
377-
node.node.info = cast(TypeInfo, replacements.get(node.node.info,
378-
node.node.info))
379-
else:
380-
# TODO: Other node types
381-
if isinstance(node.node, Var) and node.node.type:
382-
node.node.type.accept(TypeReplaceVisitor(replacements))
405+
if isinstance(node.node, Var):
406+
# Handle them here just in case these aren't exposed through the AST.
407+
# TODO: Is this necessary?
408+
fixup_var(node.node, replacements)
383409
override = node.type_override
384410
if override:
385411
override.accept(TypeReplaceVisitor(replacements))
412+
413+
414+
def fixup_var(node: Var, replacements: Dict[SymbolNode, SymbolNode]) -> None:
415+
if node.type:
416+
node.type.accept(TypeReplaceVisitor(replacements))
417+
node.info = cast(TypeInfo, replacements.get(node.info, node.info))

mypy/server/objgraph.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,6 @@
2323
'__name__',
2424
'__class__',
2525
'__dict__',
26-
27-
# Mypy specific attribute blacklists
28-
'indirection_detector',
29-
'all_types',
30-
'type_maps',
31-
'semantic_analyzer', # Semantic analyzer has stale caches
32-
'semantic_analyzer_pass3', # Semantic analyzer has stale caches
3326
}
3427

3528
# Instances of these types can't have references to other objects

mypy/server/update.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -698,9 +698,11 @@ def replace_modules_with_new_variants(
698698
for id in new_modules:
699699
new_module = new_modules[id]
700700
if id in old_modules and new_module is not None:
701-
merge_asts(old_modules[id], old_modules[id].names,
701+
preserved_module = old_modules[id]
702+
merge_asts(preserved_module, old_modules[id].names,
702703
new_module, new_module.names)
703-
manager.modules[id] = old_modules[id]
704+
manager.modules[id] = preserved_module
705+
graph[id].tree = preserved_module
704706

705707

706708
def propagate_changes_using_dependencies(

mypy/test/testmerge.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
from mypy.build import BuildManager, BuildSource, State
99
from mypy.errors import Errors, CompileError
1010
from mypy.nodes import (
11-
Node, MypyFile, SymbolTable, SymbolTableNode, TypeInfo, Expression, Var, UNBOUND_IMPORTED
11+
Node, MypyFile, SymbolTable, SymbolTableNode, TypeInfo, Expression, Var, TypeVarExpr,
12+
UNBOUND_IMPORTED
1213
)
1314
from mypy.options import Options
1415
from mypy.server.astmerge import merge_asts
@@ -86,6 +87,9 @@ def run_case(self, testcase: DataDrivenTestCase) -> None:
8687
a.extend(self.dump(manager, kind))
8788

8889
for expr in old_subexpr:
90+
if isinstance(expr, TypeVarExpr):
91+
# These are merged so we can't perform the check.
92+
continue
8993
# Verify that old AST nodes are removed from the expression type map.
9094
assert expr not in new_types
9195

mypy/traverser.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ def visit_class_def(self, o: ClassDef) -> None:
5959
for base in o.base_type_exprs:
6060
base.accept(self)
6161
o.defs.accept(self)
62+
if o.analyzed:
63+
o.analyzed.accept(self)
6264

6365
def visit_decorator(self, o: Decorator) -> None:
6466
o.func.accept(self)

mypy/types.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -242,9 +242,10 @@ def serialize(self) -> JsonDict:
242242
class TypeList(Type):
243243
"""Information about argument types and names [...].
244244
245-
This is only used for the arguments of a Callable type, i.e. for
245+
This is used for the arguments of a Callable type, i.e. for
246246
[arg, ...] in Callable[[arg, ...], ret]. This is not a real type
247-
but a syntactic AST construct.
247+
but a syntactic AST construct. UnboundTypes can also have TypeList
248+
types before they are processed into Callable types.
248249
"""
249250

250251
items = None # type: List[Type]
@@ -254,7 +255,6 @@ def __init__(self, items: List[Type], line: int = -1, column: int = -1) -> None:
254255
self.items = items
255256

256257
def accept(self, visitor: 'TypeVisitor[T]') -> T:
257-
assert isinstance(visitor, SyntheticTypeVisitor)
258258
return visitor.visit_type_list(self)
259259

260260
def serialize(self) -> JsonDict:
@@ -1503,6 +1503,11 @@ def visit_type_type(self, t: TypeType) -> T:
15031503
def visit_forwardref_type(self, t: ForwardRef) -> T:
15041504
raise RuntimeError('Internal error: unresolved forward reference')
15051505

1506+
def visit_type_list(self, t: TypeList) -> T:
1507+
# TODO: Do we need to implement this in more visitors? TypeList objects can
1508+
# exist as components of UnboundTypes.
1509+
raise self._notimplemented_helper('type_list')
1510+
15061511

15071512
class SyntheticTypeVisitor(TypeVisitor[T]):
15081513
"""A TypeVisitor that also knows how to visit synthetic AST constructs.

0 commit comments

Comments
 (0)