Skip to content

Commit 737434b

Browse files
JukkaLgvanrossum
authored andcommitted
Foundation for fine-grained incremental checking (#2838)
See #2838 for details.
1 parent 72565f0 commit 737434b

32 files changed

+4283
-129
lines changed

mypy/build.py

+13-11
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,9 @@ class BuildResult:
6565
errors: List of error messages.
6666
"""
6767

68-
def __init__(self, manager: 'BuildManager') -> None:
68+
def __init__(self, manager: 'BuildManager', graph: Graph) -> None:
6969
self.manager = manager
70+
self.graph = graph
7071
self.files = manager.modules
7172
self.types = manager.all_types
7273
self.errors = manager.errors.messages()
@@ -184,8 +185,8 @@ def build(sources: List[BuildSource],
184185
)
185186

186187
try:
187-
dispatch(sources, manager)
188-
return BuildResult(manager)
188+
graph = dispatch(sources, manager)
189+
return BuildResult(manager, graph)
189190
finally:
190191
manager.log("Build finished in %.3f seconds with %d modules, %d types, and %d errors" %
191192
(time.time() - manager.start_time,
@@ -474,7 +475,7 @@ def parse_file(self, id: str, path: str, source: str, ignore_errors: bool) -> My
474475
return tree
475476

476477
def module_not_found(self, path: str, line: int, id: str) -> None:
477-
self.errors.set_file(path)
478+
self.errors.set_file(path, id)
478479
stub_msg = "(Stub files are from https://github.com/python/typeshed)"
479480
if ((self.options.python_version[0] == 2 and moduleinfo.is_py2_std_lib_module(id)) or
480481
(self.options.python_version[0] >= 3 and moduleinfo.is_py3_std_lib_module(id))):
@@ -1230,7 +1231,7 @@ def skipping_ancestor(self, id: str, path: str, ancestor_for: 'State') -> None:
12301231
# so we'd need to cache the decision.
12311232
manager = self.manager
12321233
manager.errors.set_import_context([])
1233-
manager.errors.set_file(ancestor_for.xpath)
1234+
manager.errors.set_file(ancestor_for.xpath, ancestor_for.id)
12341235
manager.errors.report(-1, -1, "Ancestor package '%s' ignored" % (id,),
12351236
severity='note', only_once=True)
12361237
manager.errors.report(-1, -1,
@@ -1242,7 +1243,7 @@ def skipping_module(self, id: str, path: str) -> None:
12421243
manager = self.manager
12431244
save_import_context = manager.errors.import_context()
12441245
manager.errors.set_import_context(self.caller_state.import_context)
1245-
manager.errors.set_file(self.caller_state.xpath)
1246+
manager.errors.set_file(self.caller_state.xpath, self.caller_state.id)
12461247
line = self.caller_line
12471248
manager.errors.report(line, 0,
12481249
"Import of '%s' ignored" % (id,),
@@ -1429,7 +1430,7 @@ def parse_file(self) -> None:
14291430
continue
14301431
if id == '':
14311432
# Must be from a relative import.
1432-
manager.errors.set_file(self.xpath)
1433+
manager.errors.set_file(self.xpath, self.id)
14331434
manager.errors.report(line, 0,
14341435
"No parent module -- cannot perform relative import",
14351436
blocker=True)
@@ -1545,20 +1546,21 @@ def write_cache(self) -> None:
15451546
self.interface_hash = new_interface_hash
15461547

15471548

1548-
def dispatch(sources: List[BuildSource], manager: BuildManager) -> None:
1549+
def dispatch(sources: List[BuildSource], manager: BuildManager) -> Graph:
15491550
manager.log("Mypy version %s" % __version__)
15501551
graph = load_graph(sources, manager)
15511552
if not graph:
15521553
print("Nothing to do?!")
1553-
return
1554+
return graph
15541555
manager.log("Loaded graph with %d nodes" % len(graph))
15551556
if manager.options.dump_graph:
15561557
dump_graph(graph)
1557-
return
1558+
return graph
15581559
process_graph(graph, manager)
15591560
if manager.options.warn_unused_ignores:
15601561
# TODO: This could also be a per-module option.
15611562
manager.errors.generate_unused_ignore_notes()
1563+
return graph
15621564

15631565

15641566
class NodeInfo:
@@ -1633,7 +1635,7 @@ def load_graph(sources: List[BuildSource], manager: BuildManager) -> Graph:
16331635
except ModuleNotFound:
16341636
continue
16351637
if st.id in graph:
1636-
manager.errors.set_file(st.xpath)
1638+
manager.errors.set_file(st.xpath, st.id)
16371639
manager.errors.report(-1, -1, "Duplicate module named '%s'" % st.id)
16381640
manager.errors.raise_error()
16391641
graph[st.id] = st

mypy/checker.py

+59-30
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,16 @@
6565
LAST_PASS = 1 # Pass numbers start at 0
6666

6767

68-
# A node which is postponed to be type checked during the next pass.
68+
# A node which is postponed to be processed during the next pass.
69+
# This is used for both batch mode and fine-grained incremental mode.
6970
DeferredNode = NamedTuple(
7071
'DeferredNode',
7172
[
72-
('node', FuncItem),
73+
# In batch mode only FuncDef and LambdaExpr are supported
74+
('node', Union[FuncDef, LambdaExpr, MypyFile]),
7375
('context_type_name', Optional[str]), # Name of the surrounding class (for error messages)
74-
('active_class', Optional[Type]), # And its type (for selftype handling)
76+
('active_typeinfo', Optional[TypeInfo]), # And its TypeInfo (for semantic analysis
77+
# self type handling)
7578
])
7679

7780

@@ -167,7 +170,7 @@ def check_first_pass(self) -> None:
167170
168171
Deferred functions will be processed by check_second_pass().
169172
"""
170-
self.errors.set_file(self.path)
173+
self.errors.set_file(self.path, self.tree.fullname())
171174
with self.enter_partial_types():
172175
with self.binder.top_frame_context():
173176
for d in self.tree.defs:
@@ -187,38 +190,57 @@ def check_first_pass(self) -> None:
187190
self.fail(messages.ALL_MUST_BE_SEQ_STR.format(str_seq_s, all_s),
188191
all_.node)
189192

190-
def check_second_pass(self) -> bool:
193+
def check_second_pass(self, todo: List[DeferredNode] = None) -> bool:
191194
"""Run second or following pass of type checking.
192195
193196
This goes through deferred nodes, returning True if there were any.
194197
"""
195-
if not self.deferred_nodes:
198+
if not todo and not self.deferred_nodes:
196199
return False
197-
self.errors.set_file(self.path)
200+
self.errors.set_file(self.path, self.tree.fullname())
198201
self.pass_num += 1
199-
todo = self.deferred_nodes
202+
if not todo:
203+
todo = self.deferred_nodes
204+
else:
205+
assert not self.deferred_nodes
200206
self.deferred_nodes = []
201-
done = set() # type: Set[FuncItem]
202-
for node, type_name, active_class in todo:
207+
done = set() # type: Set[Union[FuncDef, LambdaExpr, MypyFile]]
208+
for node, type_name, active_typeinfo in todo:
203209
if node in done:
204210
continue
205211
# This is useful for debugging:
206212
# print("XXX in pass %d, class %s, function %s" %
207213
# (self.pass_num, type_name, node.fullname() or node.name()))
208214
done.add(node)
209215
with self.errors.enter_type(type_name) if type_name else nothing():
210-
with self.scope.push_class(active_class) if active_class else nothing():
211-
if isinstance(node, Statement):
212-
self.accept(node)
213-
elif isinstance(node, Expression):
214-
self.expr_checker.accept(node)
215-
else:
216-
assert False
216+
with self.scope.push_class(active_typeinfo) if active_typeinfo else nothing():
217+
self.check_partial(node)
217218
return True
218219

220+
def check_partial(self, node: Union[FuncDef, LambdaExpr, MypyFile]) -> None:
221+
if isinstance(node, MypyFile):
222+
self.check_top_level(node)
223+
elif isinstance(node, LambdaExpr):
224+
self.expr_checker.accept(node)
225+
else:
226+
self.accept(node)
227+
228+
def check_top_level(self, node: MypyFile) -> None:
229+
"""Check only the top-level of a module, skipping function definitions."""
230+
with self.enter_partial_types():
231+
with self.binder.top_frame_context():
232+
for d in node.defs:
233+
# TODO: Type check class bodies.
234+
if not isinstance(d, (FuncDef, ClassDef)):
235+
d.accept(self)
236+
237+
assert not self.current_node_deferred
238+
# TODO: Handle __all__
239+
219240
def handle_cannot_determine_type(self, name: str, context: Context) -> None:
220241
node = self.scope.top_function()
221-
if self.pass_num < LAST_PASS and node is not None:
242+
if (self.pass_num < LAST_PASS and node is not None
243+
and isinstance(node, (FuncDef, LambdaExpr))):
222244
# Don't report an error yet. Just defer.
223245
if self.errors.type_name:
224246
type_name = self.errors.type_name[-1]
@@ -635,7 +657,7 @@ def is_implicit_any(t: Type) -> bool:
635657
for i in range(len(typ.arg_types)):
636658
arg_type = typ.arg_types[i]
637659

638-
ref_type = self.scope.active_class()
660+
ref_type = self.scope.active_self_type() # type: Optional[Type]
639661
if (isinstance(defn, FuncDef) and ref_type is not None and i == 0
640662
and not defn.is_static
641663
and typ.arg_kinds[0] not in [nodes.ARG_STAR, nodes.ARG_STAR2]):
@@ -946,7 +968,7 @@ def check_method_override_for_base_with_name(
946968
# The name of the method is defined in the base class.
947969

948970
# Construct the type of the overriding method.
949-
typ = bind_self(self.function_type(defn), self.scope.active_class())
971+
typ = bind_self(self.function_type(defn), self.scope.active_self_type())
950972
# Map the overridden method type to subtype context so that
951973
# it can be checked for compatibility.
952974
original_type = base_attr.type
@@ -959,7 +981,7 @@ def check_method_override_for_base_with_name(
959981
assert False, str(base_attr.node)
960982
if isinstance(original_type, FunctionLike):
961983
original = map_type_from_supertype(
962-
bind_self(original_type, self.scope.active_class()),
984+
bind_self(original_type, self.scope.active_self_type()),
963985
defn.info, base)
964986
# Check that the types are compatible.
965987
# TODO overloaded signatures
@@ -1051,7 +1073,7 @@ def visit_class_def(self, defn: ClassDef) -> None:
10511073
old_binder = self.binder
10521074
self.binder = ConditionalTypeBinder()
10531075
with self.binder.top_frame_context():
1054-
with self.scope.push_class(fill_typevars(defn.info)):
1076+
with self.scope.push_class(defn.info):
10551077
self.accept(defn.defs)
10561078
self.binder = old_binder
10571079
if not defn.has_incompatible_baseclass:
@@ -1317,8 +1339,8 @@ def check_compatibility_super(self, lvalue: NameExpr, lvalue_type: Type, rvalue:
13171339
# Class-level function objects and classmethods become bound
13181340
# methods: the former to the instance, the latter to the
13191341
# class
1320-
base_type = bind_self(base_type, self.scope.active_class())
1321-
compare_type = bind_self(compare_type, self.scope.active_class())
1342+
base_type = bind_self(base_type, self.scope.active_self_type())
1343+
compare_type = bind_self(compare_type, self.scope.active_self_type())
13221344

13231345
# If we are a static method, ensure to also tell the
13241346
# lvalue it now contains a static method
@@ -1347,7 +1369,8 @@ def lvalue_type_from_base(self, expr_node: Var,
13471369

13481370
if base_type:
13491371
if not has_no_typevars(base_type):
1350-
instance = cast(Instance, self.scope.active_class())
1372+
# TODO: Handle TupleType, don't cast
1373+
instance = cast(Instance, self.scope.active_self_type())
13511374
itype = map_instance_to_supertype(instance, base)
13521375
base_type = expand_type_by_instance(base_type, itype)
13531376

@@ -2996,7 +3019,7 @@ def is_node_static(node: Node) -> Optional[bool]:
29963019

29973020
class Scope:
29983021
# We keep two stacks combined, to maintain the relative order
2999-
stack = None # type: List[Union[Type, FuncItem, MypyFile]]
3022+
stack = None # type: List[Union[TypeInfo, FuncItem, MypyFile]]
30003023

30013024
def __init__(self, module: MypyFile) -> None:
30023025
self.stack = [module]
@@ -3007,20 +3030,26 @@ def top_function(self) -> Optional[FuncItem]:
30073030
return e
30083031
return None
30093032

3010-
def active_class(self) -> Optional[Type]:
3011-
if isinstance(self.stack[-1], Type):
3033+
def active_class(self) -> Optional[TypeInfo]:
3034+
if isinstance(self.stack[-1], TypeInfo):
30123035
return self.stack[-1]
30133036
return None
30143037

3038+
def active_self_type(self) -> Optional[Union[Instance, TupleType]]:
3039+
info = self.active_class()
3040+
if info:
3041+
return fill_typevars(info)
3042+
return None
3043+
30153044
@contextmanager
30163045
def push_function(self, item: FuncItem) -> Iterator[None]:
30173046
self.stack.append(item)
30183047
yield
30193048
self.stack.pop()
30203049

30213050
@contextmanager
3022-
def push_class(self, t: Type) -> Iterator[None]:
3023-
self.stack.append(t)
3051+
def push_class(self, info: TypeInfo) -> Iterator[None]:
3052+
self.stack.append(info)
30243053
yield
30253054
self.stack.pop()
30263055

0 commit comments

Comments
 (0)