Skip to content

Commit 14d7555

Browse files
committed
Don't produce spurious unused type ignore messages in incremental mode
This is accomplished by generating diagnostics for suppressed dependencies before generating unused ignore notes. This requires that we store line information for suppressed dependencies in our cache files. Fixes #2960.
1 parent db02926 commit 14d7555

File tree

2 files changed

+37
-14
lines changed

2 files changed

+37
-14
lines changed

mypy/build.py

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,8 @@ def default_lib_path(data_dir: str,
396396
('suppressed', List[str]), # dependencies that weren't imported
397397
('child_modules', List[str]), # all submodules of the given module
398398
('options', Optional[Dict[str, object]]), # build options
399+
# dep_prios and dep_lines are in parallel with
400+
# dependencies + suppressed.
399401
('dep_prios', List[int]),
400402
('dep_lines', List[int]),
401403
('interface_hash', str), # hash representing the public interface
@@ -964,8 +966,8 @@ def find_cache_meta(id: str, path: str, manager: BuildManager) -> Optional[Cache
964966
# Ignore cache if generated by an older mypy version.
965967
if ((m.version_id != manager.version_id and not manager.options.skip_version_check)
966968
or m.options is None
967-
or len(m.dependencies) != len(m.dep_prios)
968-
or len(m.dependencies) != len(m.dep_lines)):
969+
or len(m.dependencies) + len(m.suppressed) != len(m.dep_prios)
970+
or len(m.dependencies) + len(m.suppressed) != len(m.dep_lines)):
969971
manager.log('Metadata abandoned for {}: new attributes are missing'.format(id))
970972
return None
971973

@@ -1514,12 +1516,13 @@ def __init__(self,
15141516
# compare them to the originals later.
15151517
self.dependencies = list(self.meta.dependencies)
15161518
self.suppressed = list(self.meta.suppressed)
1517-
assert len(self.meta.dependencies) == len(self.meta.dep_prios)
1519+
all_deps = self.dependencies + self.suppressed
1520+
assert len(all_deps) == len(self.meta.dep_prios)
15181521
self.priorities = {id: pri
1519-
for id, pri in zip(self.meta.dependencies, self.meta.dep_prios)}
1520-
assert len(self.meta.dependencies) == len(self.meta.dep_lines)
1522+
for id, pri in zip(all_deps, self.meta.dep_prios)}
1523+
assert len(all_deps) == len(self.meta.dep_lines)
15211524
self.dep_line_map = {id: line
1522-
for id, line in zip(self.meta.dependencies, self.meta.dep_lines)}
1525+
for id, line in zip(all_deps, self.meta.dep_lines)}
15231526
self.child_modules = set(self.meta.child_modules)
15241527
else:
15251528
# When doing a fine-grained cache load, pretend we only
@@ -1909,14 +1912,18 @@ def write_cache(self) -> None:
19091912
self.mark_interface_stale()
19101913
self.interface_hash = new_interface_hash
19111914

1912-
def verify_dependencies(self) -> None:
1915+
def verify_dependencies(self, suppressed_only: bool=False) -> None:
19131916
"""Report errors for import targets in modules that don't exist."""
1914-
# Strip out indirect dependencies. See comment in build.load_graph().
19151917
manager = self.manager
1916-
dependencies = [dep for dep in self.dependencies
1917-
if self.priorities.get(dep) != PRI_INDIRECT]
19181918
assert self.ancestors is not None
1919-
for dep in dependencies + self.suppressed + self.ancestors:
1919+
if suppressed_only:
1920+
all_deps = self.suppressed
1921+
else:
1922+
# Strip out indirect dependencies. See comment in build.load_graph().
1923+
dependencies = [dep for dep in self.dependencies
1924+
if self.priorities.get(dep) != PRI_INDIRECT]
1925+
all_deps = dependencies + self.suppressed + self.ancestors
1926+
for dep in all_deps:
19201927
options = manager.options.clone_for_module(dep)
19211928
if dep not in manager.modules and not options.ignore_missing_imports:
19221929
line = self.dep_line_map.get(dep, 1)
@@ -1939,13 +1946,18 @@ def verify_dependencies(self) -> None:
19391946
pass
19401947

19411948
def dependency_priorities(self) -> List[int]:
1942-
return [self.priorities.get(dep, PRI_HIGH) for dep in self.dependencies]
1949+
return [self.priorities.get(dep, PRI_HIGH) for dep in self.dependencies + self.suppressed]
19431950

19441951
def dependency_lines(self) -> List[int]:
1945-
return [self.dep_line_map.get(dep, 1) for dep in self.dependencies]
1952+
return [self.dep_line_map.get(dep, 1) for dep in self.dependencies + self.suppressed]
19461953

19471954
def generate_unused_ignore_notes(self) -> None:
19481955
if self.options.warn_unused_ignores:
1956+
# If this file was initially loaded from the cache, it may have suppressed
1957+
# dependencies due to imports with ignores on them. We need to generate
1958+
# those errors to avoid spuriously them as unused ignores.
1959+
if self.meta:
1960+
self.verify_dependencies(suppressed_only=True)
19491961
self.manager.errors.generate_unused_ignore_notes(self.xpath)
19501962

19511963

test-data/unit/check-incremental.test

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
-- annotation, and any files expect to be stale (aka have a modified interface)
2929
-- should be annotated in the [stale] annotation. Note that a file that ends up
3030
-- producing an error has its caches deleted and is marked stale automatically.
31-
-- Such files don't need to be included in [stale ...] list.
31+
-- Such files do not need to be included in [stale ...] list.
3232
--
3333
-- The test suite will automatically assume that __main__ is stale and rechecked in
3434
-- all cases so we can avoid constantly having to annotate it. The list of
@@ -4206,3 +4206,14 @@ class Two:
42064206
pass
42074207
[out2]
42084208
tmp/m/two.py:2: error: Revealed type is 'builtins.str'
4209+
4210+
[case testImportUnusedIgnore]
4211+
# flags: --warn-unused-ignores
4212+
import a
4213+
[file a.py]
4214+
import b
4215+
import foo # type: ignore
4216+
[file b.py]
4217+
x = 1
4218+
[file b.py.2]
4219+
x = '2'

0 commit comments

Comments
 (0)