Skip to content

Fix incremental speed regression #4331

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

Merged
merged 5 commits into from
Dec 8, 2017
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
20 changes: 19 additions & 1 deletion mypy/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -1474,6 +1474,9 @@ class State:
# Whether to ignore all errors
ignore_all = False

# Whether the module has an error or any of its dependencies have one.
transitive_error = False

# Type checker used for checking this file. Use type_checker() for
# access and to construct this on demand.
_type_checker = None # type: Optional[TypeChecker]
Expand Down Expand Up @@ -1944,7 +1947,7 @@ def write_cache(self) -> None:
if self.manager.options.quick_and_dirty:
is_errors = self.manager.errors.is_errors_for_file(self.path)
else:
is_errors = self.manager.errors.is_errors()
is_errors = self.transitive_error
if is_errors:
delete_cache(self.id, self.path, self.manager)
self.meta = None
Expand Down Expand Up @@ -2257,6 +2260,12 @@ def process_graph(graph: Graph, manager: BuildManager) -> None:
else:
fresh_msg = "stale due to deps (%s)" % " ".join(sorted(stale_deps))

# Initialize transitive_error for all SCC members from union
# of transitive_error of dependencies.
if any(graph[dep].transitive_error for dep in deps if dep in graph):
for id in scc:
graph[id].transitive_error = True

scc_str = " ".join(scc)
if fresh:
if not maybe_reuse_in_memory_tree(graph, scc, manager):
Expand All @@ -2272,6 +2281,11 @@ def process_graph(graph: Graph, manager: BuildManager) -> None:
# single fresh SCC. This is intentional -- we don't need those modules
# loaded if there are no more stale SCCs to be rechecked.
#
# Also note we shouldn't have to worry about transitive_error here,
# since modules with transitive errors aren't written to the cache,
# and if any dependencies were changed, this SCC would be stale.
# (Also, in quick_and_dirty mode we don't care about transitive errors.)
#
# TODO: see if it's possible to determine if we need to process only a
# _subset_ of the past SCCs instead of having to process them all.
for prev_scc in fresh_scc_queue:
Expand Down Expand Up @@ -2416,6 +2430,7 @@ def can_reuse_in_memory_tree(graph: Graph, scc: List[str], manager: BuildManager
# Check that all dependencies were loaded from memory.
# If not, some dependency was reparsed but the interface hash
# wasn't changed -- in that case we can't reuse the tree.
# TODO: Pass deps in from process_graph(), via maybe_reuse_in_memory_tree()?
deps = set(dep for id in scc for dep in graph[id].dependencies if dep in graph)
deps -= set(scc) # Subtract the SCC itself (else nothing will be safe)
if all(graph[dep].is_from_saved_cache for dep in deps):
Expand Down Expand Up @@ -2464,6 +2479,9 @@ def process_stale_scc(graph: Graph, scc: List[str], manager: BuildManager) -> No
for id in stale:
if graph[id].type_check_second_pass():
more = True
if any(manager.errors.is_errors_for_file(graph[id].xpath) for id in stale):
for id in stale:
graph[id].transitive_error = True
for id in stale:
graph[id].finish_passes()
graph[id].write_cache()
Expand Down
21 changes: 18 additions & 3 deletions test-data/unit/check-incremental.test
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
-- Checks for incremental mode (see testcheck.py).
-- Each test is run twice, once with a cold cache, once with a warm cache.
-- Each test is run at least twice, once with a cold cache, once with a warm cache.
-- Before the tests are run again, in step N any *.py.N files are copied to
-- *.py.
-- *.py. There are at least two runs; more as long as there are *.py.N files.
--
-- You can add an empty section like `[delete mod.py.2]` to delete `mod.py`
-- before the second run.
Expand Down Expand Up @@ -1113,7 +1113,7 @@ val = "foo"

[builtins fixtures/module_all.pyi]
[rechecked main, c, c.submodule]
[stale]
[stale c]
[out2]
tmp/c/submodule.py:2: error: Incompatible types in assignment (expression has type "str", variable has type "int")
tmp/main.py:7: error: "C" has no attribute "foo"
Expand Down Expand Up @@ -3432,3 +3432,18 @@ extra = 1

[out1]
[out2]

[case testErrorsAffectDependentsOnly]
# cmd: mypy -m m.a m.b m.c
[file m/__init__.py]
[file m/a.py]
1 + '' # Deliberate error
[file m/b.py]
import m.a # Depends on module with error
[file m/c.py]
import m # No error here
[rechecked m.a, m.b]
[out1]
tmp/m/a.py:1: error: Unsupported operand types for + ("int" and "str")
[out2]
tmp/m/a.py:1: error: Unsupported operand types for + ("int" and "str")