Skip to content

Commit b9daa31

Browse files
JukkaLsvalentin
authored andcommitted
Don't ignore errors in files passed on the command line (#14060)
#13768 had a bug so that errors were sometimes silenced in files that were under a directory in `sys.path`. `sys.path` sometimes includes the current working directory, resulting in no errors reported at all. Fix it by always reporting errors if a file was passed on the command line (unless *explicitly* silenced). When using import following errors can still be ignored, which is questionable, but this didn't change recently so I'm not addressing it here. Fixes #14042.
1 parent 02fd8a5 commit b9daa31

File tree

6 files changed

+108
-25
lines changed

6 files changed

+108
-25
lines changed

mypy/build.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1940,7 +1940,7 @@ def __init__(
19401940
raise
19411941
if follow_imports == "silent":
19421942
self.ignore_all = True
1943-
elif path and is_silent_import_module(manager, path):
1943+
elif path and is_silent_import_module(manager, path) and not root_source:
19441944
self.ignore_all = True
19451945
self.path = path
19461946
if path:
@@ -2629,7 +2629,7 @@ def find_module_and_diagnose(
26292629
else:
26302630
skipping_module(manager, caller_line, caller_state, id, result)
26312631
raise ModuleNotFound
2632-
if is_silent_import_module(manager, result):
2632+
if is_silent_import_module(manager, result) and not root_source:
26332633
follow_imports = "silent"
26342634
return (result, follow_imports)
26352635
else:
@@ -3024,7 +3024,11 @@ def load_graph(
30243024
for bs in sources:
30253025
try:
30263026
st = State(
3027-
id=bs.module, path=bs.path, source=bs.text, manager=manager, root_source=True
3027+
id=bs.module,
3028+
path=bs.path,
3029+
source=bs.text,
3030+
manager=manager,
3031+
root_source=not bs.followed,
30283032
)
30293033
except ModuleNotFound:
30303034
continue

mypy/dmypy_server.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -592,7 +592,7 @@ def fine_grained_increment_follow_imports(self, sources: list[BuildSource]) -> l
592592
sources.extend(new_files)
593593

594594
# Process changes directly reachable from roots.
595-
messages = fine_grained_manager.update(changed, [])
595+
messages = fine_grained_manager.update(changed, [], followed=True)
596596

597597
# Follow deps from changed modules (still within graph).
598598
worklist = changed[:]
@@ -609,13 +609,13 @@ def fine_grained_increment_follow_imports(self, sources: list[BuildSource]) -> l
609609
sources2, graph, seen, changed_paths
610610
)
611611
self.update_sources(new_files)
612-
messages = fine_grained_manager.update(changed, [])
612+
messages = fine_grained_manager.update(changed, [], followed=True)
613613
worklist.extend(changed)
614614

615615
t2 = time.time()
616616

617617
def refresh_file(module: str, path: str) -> list[str]:
618-
return fine_grained_manager.update([(module, path)], [])
618+
return fine_grained_manager.update([(module, path)], [], followed=True)
619619

620620
for module_id, state in list(graph.items()):
621621
new_messages = refresh_suppressed_submodules(
@@ -632,10 +632,10 @@ def refresh_file(module: str, path: str) -> list[str]:
632632
new_unsuppressed = self.find_added_suppressed(graph, seen, manager.search_paths)
633633
if not new_unsuppressed:
634634
break
635-
new_files = [BuildSource(mod[1], mod[0]) for mod in new_unsuppressed]
635+
new_files = [BuildSource(mod[1], mod[0], followed=True) for mod in new_unsuppressed]
636636
sources.extend(new_files)
637637
self.update_sources(new_files)
638-
messages = fine_grained_manager.update(new_unsuppressed, [])
638+
messages = fine_grained_manager.update(new_unsuppressed, [], followed=True)
639639

640640
for module_id, path in new_unsuppressed:
641641
new_messages = refresh_suppressed_submodules(
@@ -717,15 +717,15 @@ def find_reachable_changed_modules(
717717
for dep in state.dependencies:
718718
if dep not in seen:
719719
seen.add(dep)
720-
worklist.append(BuildSource(graph[dep].path, graph[dep].id))
720+
worklist.append(BuildSource(graph[dep].path, graph[dep].id, followed=True))
721721
return changed, new_files
722722

723723
def direct_imports(
724724
self, module: tuple[str, str], graph: mypy.build.Graph
725725
) -> list[BuildSource]:
726726
"""Return the direct imports of module not included in seen."""
727727
state = graph[module[0]]
728-
return [BuildSource(graph[dep].path, dep) for dep in state.dependencies]
728+
return [BuildSource(graph[dep].path, dep, followed=True) for dep in state.dependencies]
729729

730730
def find_added_suppressed(
731731
self, graph: mypy.build.Graph, seen: set[str], search_paths: SearchPaths

mypy/modulefinder.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,15 +115,19 @@ def __init__(
115115
module: str | None,
116116
text: str | None = None,
117117
base_dir: str | None = None,
118+
followed: bool = False,
118119
) -> None:
119120
self.path = path # File where it's found (e.g. 'xxx/yyy/foo/bar.py')
120121
self.module = module or "__main__" # Module name (e.g. 'foo.bar')
121122
self.text = text # Source code, if initially supplied, else None
122123
self.base_dir = base_dir # Directory where the package is rooted (e.g. 'xxx/yyy')
124+
self.followed = followed # Was this found by following imports?
123125

124126
def __repr__(self) -> str:
125-
return "BuildSource(path={!r}, module={!r}, has_text={}, base_dir={!r})".format(
126-
self.path, self.module, self.text is not None, self.base_dir
127+
return (
128+
"BuildSource(path={!r}, module={!r}, has_text={}, base_dir={!r}, followed={})".format(
129+
self.path, self.module, self.text is not None, self.base_dir, self.followed
130+
)
127131
)
128132

129133

mypy/server/update.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,10 @@ def __init__(self, result: BuildResult) -> None:
203203
self.processed_targets: list[str] = []
204204

205205
def update(
206-
self, changed_modules: list[tuple[str, str]], removed_modules: list[tuple[str, str]]
206+
self,
207+
changed_modules: list[tuple[str, str]],
208+
removed_modules: list[tuple[str, str]],
209+
followed: bool = False,
207210
) -> list[str]:
208211
"""Update previous build result by processing changed modules.
209212
@@ -219,6 +222,7 @@ def update(
219222
Assume this is correct; it's not validated here.
220223
removed_modules: Modules that have been deleted since the previous update
221224
or removed from the build.
225+
followed: If True, the modules were found through following imports
222226
223227
Returns:
224228
A list of errors.
@@ -256,7 +260,9 @@ def update(
256260
self.blocking_error = None
257261

258262
while True:
259-
result = self.update_one(changed_modules, initial_set, removed_set, blocking_error)
263+
result = self.update_one(
264+
changed_modules, initial_set, removed_set, blocking_error, followed
265+
)
260266
changed_modules, (next_id, next_path), blocker_messages = result
261267

262268
if blocker_messages is not None:
@@ -329,6 +335,7 @@ def update_one(
329335
initial_set: set[str],
330336
removed_set: set[str],
331337
blocking_error: str | None,
338+
followed: bool,
332339
) -> tuple[list[tuple[str, str]], tuple[str, str], list[str] | None]:
333340
"""Process a module from the list of changed modules.
334341
@@ -355,7 +362,7 @@ def update_one(
355362
)
356363
return changed_modules, (next_id, next_path), None
357364

358-
result = self.update_module(next_id, next_path, next_id in removed_set)
365+
result = self.update_module(next_id, next_path, next_id in removed_set, followed)
359366
remaining, (next_id, next_path), blocker_messages = result
360367
changed_modules = [(id, path) for id, path in changed_modules if id != next_id]
361368
changed_modules = dedupe_modules(remaining + changed_modules)
@@ -368,7 +375,7 @@ def update_one(
368375
return changed_modules, (next_id, next_path), blocker_messages
369376

370377
def update_module(
371-
self, module: str, path: str, force_removed: bool
378+
self, module: str, path: str, force_removed: bool, followed: bool
372379
) -> tuple[list[tuple[str, str]], tuple[str, str], list[str] | None]:
373380
"""Update a single modified module.
374381
@@ -380,6 +387,7 @@ def update_module(
380387
path: File system path of the module
381388
force_removed: If True, consider module removed from the build even if path
382389
exists (used for removing an existing file from the build)
390+
followed: Was this found via import following?
383391
384392
Returns:
385393
Tuple with these items:
@@ -417,7 +425,7 @@ def update_module(
417425
manager.errors.reset()
418426
self.processed_targets.append(module)
419427
result = update_module_isolated(
420-
module, path, manager, previous_modules, graph, force_removed
428+
module, path, manager, previous_modules, graph, force_removed, followed
421429
)
422430
if isinstance(result, BlockedUpdate):
423431
# Blocking error -- just give up
@@ -552,6 +560,7 @@ def update_module_isolated(
552560
previous_modules: dict[str, str],
553561
graph: Graph,
554562
force_removed: bool,
563+
followed: bool,
555564
) -> UpdateResult:
556565
"""Build a new version of one changed module only.
557566
@@ -575,7 +584,7 @@ def update_module_isolated(
575584
delete_module(module, path, graph, manager)
576585
return NormalUpdate(module, path, [], None)
577586

578-
sources = get_sources(manager.fscache, previous_modules, [(module, path)])
587+
sources = get_sources(manager.fscache, previous_modules, [(module, path)], followed)
579588

580589
if module in manager.missing_modules:
581590
manager.missing_modules.remove(module)
@@ -728,12 +737,15 @@ def get_module_to_path_map(graph: Graph) -> dict[str, str]:
728737

729738

730739
def get_sources(
731-
fscache: FileSystemCache, modules: dict[str, str], changed_modules: list[tuple[str, str]]
740+
fscache: FileSystemCache,
741+
modules: dict[str, str],
742+
changed_modules: list[tuple[str, str]],
743+
followed: bool,
732744
) -> list[BuildSource]:
733745
sources = []
734746
for id, path in changed_modules:
735747
if fscache.isfile(path):
736-
sources.append(BuildSource(path, id, None))
748+
sources.append(BuildSource(path, id, None, followed=followed))
737749
return sources
738750

739751

mypy/test/testcmdline.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,10 @@ def test_python_cmdline(testcase: DataDrivenTestCase, step: int) -> None:
6969
env["PYTHONPATH"] = PREFIX
7070
if os.path.isdir(extra_path):
7171
env["PYTHONPATH"] += os.pathsep + extra_path
72+
cwd = os.path.join(test_temp_dir, custom_cwd or "")
73+
args = [arg.replace("$CWD", os.path.abspath(cwd)) for arg in args]
7274
process = subprocess.Popen(
73-
fixed + args,
74-
stdout=subprocess.PIPE,
75-
stderr=subprocess.PIPE,
76-
cwd=os.path.join(test_temp_dir, custom_cwd or ""),
77-
env=env,
75+
fixed + args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd, env=env
7876
)
7977
outb, errb = process.communicate()
8078
result = process.returncode

test-data/unit/cmdline.test

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1505,3 +1505,68 @@ def f():
15051505
[out]
15061506
a.py:2: note: By default the bodies of untyped functions are not checked, consider using --check-untyped-defs
15071507
== Return code: 0
1508+
1509+
[case testCustomTypeshedDirFilePassedExplicitly]
1510+
# cmd: mypy --custom-typeshed-dir dir m.py dir/stdlib/foo.pyi
1511+
[file m.py]
1512+
1()
1513+
[file dir/stdlib/abc.pyi]
1514+
1() # Errors are not reported from typeshed by default
1515+
[file dir/stdlib/builtins.pyi]
1516+
class object: pass
1517+
class str(object): pass
1518+
class int(object): pass
1519+
[file dir/stdlib/sys.pyi]
1520+
[file dir/stdlib/types.pyi]
1521+
[file dir/stdlib/typing.pyi]
1522+
[file dir/stdlib/mypy_extensions.pyi]
1523+
[file dir/stdlib/typing_extensions.pyi]
1524+
[file dir/stdlib/foo.pyi]
1525+
1() # Errors are reported if the file was explicitly passed on the command line
1526+
[file dir/stdlib/VERSIONS]
1527+
[out]
1528+
dir/stdlib/foo.pyi:1: error: "int" not callable
1529+
m.py:1: error: "int" not callable
1530+
1531+
[case testFileInPythonPathPassedExplicitly1]
1532+
# cmd: mypy $CWD/pypath/foo.py
1533+
[file pypath/foo.py]
1534+
1()
1535+
[out]
1536+
pypath/foo.py:1: error: "int" not callable
1537+
1538+
[case testFileInPythonPathPassedExplicitly2]
1539+
# cmd: mypy pypath/foo.py
1540+
[file pypath/foo.py]
1541+
1()
1542+
[out]
1543+
pypath/foo.py:1: error: "int" not callable
1544+
1545+
[case testFileInPythonPathPassedExplicitly3]
1546+
# cmd: mypy -p foo
1547+
# cwd: pypath
1548+
[file pypath/foo/__init__.py]
1549+
1()
1550+
[file pypath/foo/m.py]
1551+
1()
1552+
[out]
1553+
foo/m.py:1: error: "int" not callable
1554+
foo/__init__.py:1: error: "int" not callable
1555+
1556+
[case testFileInPythonPathPassedExplicitly4]
1557+
# cmd: mypy -m foo
1558+
# cwd: pypath
1559+
[file pypath/foo.py]
1560+
1()
1561+
[out]
1562+
foo.py:1: error: "int" not callable
1563+
1564+
[case testFileInPythonPathPassedExplicitly5]
1565+
# cmd: mypy -m foo.m
1566+
# cwd: pypath
1567+
[file pypath/foo/__init__.py]
1568+
1() # TODO: Maybe this should generate errors as well? But how would we decide?
1569+
[file pypath/foo/m.py]
1570+
1()
1571+
[out]
1572+
foo/m.py:1: error: "int" not callable

0 commit comments

Comments
 (0)