Skip to content

Commit 38539e5

Browse files
Merge branch 'master' into test-range
2 parents 7aca7ce + 19697af commit 38539e5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1899
-451
lines changed

.github/workflows/docs.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ on:
1212
# so it's important to do the docs build on all PRs touching mypy/errorcodes.py
1313
# in case somebody's adding a new error code without any docs
1414
- 'mypy/errorcodes.py'
15+
# Part of the documentation is automatically generated from the options
16+
# definitions in mypy/main.py
17+
- 'mypy/main.py'
1518
- 'mypyc/doc/**'
1619
- '**/*.rst'
1720
- '**/*.md'

.github/workflows/mypy_primer.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ jobs:
6767
--debug \
6868
--additional-flags="--debug-serialize" \
6969
--output concise \
70+
--mypy-install-librt \
7071
| tee diff_${{ matrix.shard-index }}.txt
7172
) || [ $? -eq 1 ]
7273
- if: ${{ matrix.shard-index == 0 }}

CHANGELOG.md

Lines changed: 214 additions & 131 deletions
Large diffs are not rendered by default.

docs/source/command_line.rst

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1255,12 +1255,18 @@ Miscellaneous
12551255
stub packages were found, they are installed and then another run
12561256
is performed.
12571257

1258-
.. option:: --junit-xml JUNIT_XML
1258+
.. option:: --junit-xml JUNIT_XML_OUTPUT_FILE
12591259

12601260
Causes mypy to generate a JUnit XML test result document with
12611261
type checking results. This can make it easier to integrate mypy
12621262
with continuous integration (CI) tools.
12631263

1264+
.. option:: --junit-format {global,per_file}
1265+
1266+
If --junit-xml is set, specifies format.
1267+
global (default): single test with all errors;
1268+
per_file: one test entry per file with failures.
1269+
12641270
.. option:: --find-occurrences CLASS.MEMBER
12651271

12661272
This flag will make mypy print out all usages of a class member

docs/source/config_file.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1153,6 +1153,15 @@ These options may only be set in the global section (``[mypy]``).
11531153
type checking results. This can make it easier to integrate mypy
11541154
with continuous integration (CI) tools.
11551155

1156+
.. confval:: junit_format
1157+
1158+
:type: string
1159+
:default: ``global``
1160+
1161+
If junit_xml is set, specifies format.
1162+
global (default): single test with all errors;
1163+
per_file: one test entry per file with failures.
1164+
11561165
.. confval:: scripts_are_modules
11571166

11581167
:type: boolean

misc/analyze_cache.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def extract(chunks: Iterable[JsonDict]) -> Iterable[JsonDict]:
4141
if isinstance(chunk, dict):
4242
yield chunk
4343
yield from extract(chunk.values())
44-
elif isinstance(chunk, list):
44+
elif isinstance(chunk, list): # type: ignore[unreachable] #TODO: is this actually unreachable, or are our types wrong?
4545
yield from extract(chunk)
4646

4747
yield from extract([chunk.data for chunk in chunks])
@@ -93,7 +93,7 @@ def compress(chunk: JsonDict) -> JsonDict:
9393
def helper(chunk: JsonDict) -> JsonDict:
9494
nonlocal counter
9595
if not isinstance(chunk, dict):
96-
return chunk
96+
return chunk # type: ignore[unreachable] #TODO: is this actually unreachable, or are our types wrong?
9797

9898
if len(chunk) <= 2:
9999
return chunk
@@ -124,7 +124,7 @@ def decompress(chunk: JsonDict) -> JsonDict:
124124

125125
def helper(chunk: JsonDict) -> JsonDict:
126126
if not isinstance(chunk, dict):
127-
return chunk
127+
return chunk # type: ignore[unreachable] #TODO: is this actually unreachable, or are our types wrong?
128128
if ".id" in chunk:
129129
return cache[chunk[".id"]]
130130

misc/profile_check.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -78,22 +78,22 @@ def check_requirements() -> None:
7878
if sys.platform != "linux":
7979
# TODO: How to make this work on other platforms?
8080
sys.exit("error: Only Linux is supported")
81-
82-
try:
83-
subprocess.run(["perf", "-h"], capture_output=True)
84-
except (subprocess.CalledProcessError, FileNotFoundError):
85-
print("error: The 'perf' profiler is not installed")
86-
sys.exit(1)
87-
88-
try:
89-
subprocess.run(["clang", "--version"], capture_output=True)
90-
except (subprocess.CalledProcessError, FileNotFoundError):
91-
print("error: The clang compiler is not installed")
92-
sys.exit(1)
93-
94-
if not os.path.isfile("mypy_self_check.ini"):
95-
print("error: Run this in the mypy repository root")
96-
sys.exit(1)
81+
else: # fun fact/todo: we have to use else here, because of https://github.com/python/mypy/issues/10773
82+
try:
83+
subprocess.run(["perf", "-h"], capture_output=True)
84+
except (subprocess.CalledProcessError, FileNotFoundError):
85+
print("error: The 'perf' profiler is not installed")
86+
sys.exit(1)
87+
88+
try:
89+
subprocess.run(["clang", "--version"], capture_output=True)
90+
except (subprocess.CalledProcessError, FileNotFoundError):
91+
print("error: The clang compiler is not installed")
92+
sys.exit(1)
93+
94+
if not os.path.isfile("mypy_self_check.ini"):
95+
print("error: Run this in the mypy repository root")
96+
sys.exit(1)
9797

9898

9999
def main() -> None:

mypy/build.py

Lines changed: 19 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515

1616
import collections
1717
import contextlib
18-
import errno
1918
import gc
2019
import json
2120
import os
@@ -337,6 +336,7 @@ class CacheMeta(NamedTuple):
337336
dep_lines: list[int]
338337
dep_hashes: dict[str, str]
339338
interface_hash: str # hash representing the public interface
339+
error_lines: list[str]
340340
version_id: str # mypy version for cache invalidation
341341
ignore_all: bool # if errors were ignored
342342
plugin_data: Any # config data from plugins
@@ -376,6 +376,7 @@ def cache_meta_from_dict(meta: dict[str, Any], data_json: str) -> CacheMeta:
376376
meta.get("dep_lines", []),
377377
meta.get("dep_hashes", {}),
378378
meta.get("interface_hash", ""),
379+
meta.get("error_lines", []),
379380
meta.get("version_id", sentinel),
380381
meta.get("ignore_all", True),
381382
meta.get("plugin_data", None),
@@ -1502,6 +1503,7 @@ def validate_meta(
15021503
"dep_lines": meta.dep_lines,
15031504
"dep_hashes": meta.dep_hashes,
15041505
"interface_hash": meta.interface_hash,
1506+
"error_lines": meta.error_lines,
15051507
"version_id": manager.version_id,
15061508
"ignore_all": meta.ignore_all,
15071509
"plugin_data": meta.plugin_data,
@@ -1678,28 +1680,6 @@ def write_cache_meta(
16781680
return cache_meta_from_dict(meta, data_json)
16791681

16801682

1681-
def delete_cache(id: str, path: str, manager: BuildManager) -> None:
1682-
"""Delete cache files for a module.
1683-
1684-
The cache files for a module are deleted when mypy finds errors there.
1685-
This avoids inconsistent states with cache files from different mypy runs,
1686-
see #4043 for an example.
1687-
"""
1688-
# We don't delete .deps files on errors, since the dependencies
1689-
# are mostly generated from other files and the metadata is
1690-
# tracked separately.
1691-
meta_path, data_path, _ = get_cache_names(id, path, manager.options)
1692-
cache_paths = [meta_path, data_path]
1693-
manager.log(f"Deleting {id} {path} {' '.join(x for x in cache_paths if x)}")
1694-
1695-
for filename in cache_paths:
1696-
try:
1697-
manager.metastore.remove(filename)
1698-
except OSError as e:
1699-
if e.errno != errno.ENOENT:
1700-
manager.log(f"Error deleting cache file {filename}: {e.strerror}")
1701-
1702-
17031683
"""Dependency manager.
17041684
17051685
Design
@@ -1875,6 +1855,9 @@ class State:
18751855
# Map from dependency id to its last observed interface hash
18761856
dep_hashes: dict[str, str] = {}
18771857

1858+
# List of errors reported for this file last time.
1859+
error_lines: list[str] = []
1860+
18781861
# Parent package, its parent, etc.
18791862
ancestors: list[str] | None = None
18801863

@@ -1896,9 +1879,6 @@ class State:
18961879
# Whether to ignore all errors
18971880
ignore_all = False
18981881

1899-
# Whether the module has an error or any of its dependencies have one.
1900-
transitive_error = False
1901-
19021882
# Errors reported before semantic analysis, to allow fine-grained
19031883
# mode to keep reporting them.
19041884
early_errors: list[ErrorInfo]
@@ -2000,6 +1980,7 @@ def __init__(
20001980
assert len(all_deps) == len(self.meta.dep_lines)
20011981
self.dep_line_map = {id: line for id, line in zip(all_deps, self.meta.dep_lines)}
20021982
self.dep_hashes = self.meta.dep_hashes
1983+
self.error_lines = self.meta.error_lines
20031984
if temporary:
20041985
self.load_tree(temporary=True)
20051986
if not manager.use_fine_grained_cache():
@@ -2517,11 +2498,6 @@ def write_cache(self) -> tuple[dict[str, Any], str, str] | None:
25172498
print(f"Error serializing {self.id}", file=self.manager.stdout)
25182499
raise # Propagate to display traceback
25192500
return None
2520-
is_errors = self.transitive_error
2521-
if is_errors:
2522-
delete_cache(self.id, self.path, self.manager)
2523-
self.meta = None
2524-
return None
25252501
dep_prios = self.dependency_priorities()
25262502
dep_lines = self.dependency_lines()
25272503
assert self.source_hash is not None
@@ -3303,34 +3279,7 @@ def process_graph(graph: Graph, manager: BuildManager) -> None:
33033279
if undeps:
33043280
fresh = False
33053281
if fresh:
3306-
# All cache files are fresh. Check that no dependency's
3307-
# cache file is newer than any scc node's cache file.
3308-
oldest_in_scc = min(graph[id].xmeta.data_mtime for id in scc)
3309-
viable = {id for id in stale_deps if graph[id].meta is not None}
3310-
newest_in_deps = (
3311-
0 if not viable else max(graph[dep].xmeta.data_mtime for dep in viable)
3312-
)
3313-
if manager.options.verbosity >= 3: # Dump all mtimes for extreme debugging.
3314-
all_ids = sorted(ascc | viable, key=lambda id: graph[id].xmeta.data_mtime)
3315-
for id in all_ids:
3316-
if id in scc:
3317-
if graph[id].xmeta.data_mtime < newest_in_deps:
3318-
key = "*id:"
3319-
else:
3320-
key = "id:"
3321-
else:
3322-
if graph[id].xmeta.data_mtime > oldest_in_scc:
3323-
key = "+dep:"
3324-
else:
3325-
key = "dep:"
3326-
manager.trace(" %5s %.0f %s" % (key, graph[id].xmeta.data_mtime, id))
3327-
# If equal, give the benefit of the doubt, due to 1-sec time granularity
3328-
# (on some platforms).
3329-
if oldest_in_scc < newest_in_deps:
3330-
fresh = False
3331-
fresh_msg = f"out of date by {newest_in_deps - oldest_in_scc:.0f} seconds"
3332-
else:
3333-
fresh_msg = "fresh"
3282+
fresh_msg = "fresh"
33343283
elif undeps:
33353284
fresh_msg = f"stale due to changed suppression ({' '.join(sorted(undeps))})"
33363285
elif stale_scc:
@@ -3342,15 +3291,14 @@ def process_graph(graph: Graph, manager: BuildManager) -> None:
33423291
else:
33433292
fresh_msg = f"stale due to deps ({' '.join(sorted(stale_deps))})"
33443293

3345-
# Initialize transitive_error for all SCC members from union
3346-
# of transitive_error of dependencies.
3347-
if any(graph[dep].transitive_error for dep in deps if dep in graph):
3348-
for id in scc:
3349-
graph[id].transitive_error = True
3350-
33513294
scc_str = " ".join(scc)
33523295
if fresh:
33533296
manager.trace(f"Queuing {fresh_msg} SCC ({scc_str})")
3297+
for id in scc:
3298+
if graph[id].error_lines:
3299+
manager.flush_errors(
3300+
manager.errors.simplify_path(graph[id].xpath), graph[id].error_lines, False
3301+
)
33543302
fresh_scc_queue.append(scc)
33553303
else:
33563304
if fresh_scc_queue:
@@ -3362,11 +3310,6 @@ def process_graph(graph: Graph, manager: BuildManager) -> None:
33623310
# single fresh SCC. This is intentional -- we don't need those modules
33633311
# loaded if there are no more stale SCCs to be rechecked.
33643312
#
3365-
# Also note we shouldn't have to worry about transitive_error here,
3366-
# since modules with transitive errors aren't written to the cache,
3367-
# and if any dependencies were changed, this SCC would be stale.
3368-
# (Also, in quick_and_dirty mode we don't care about transitive errors.)
3369-
#
33703313
# TODO: see if it's possible to determine if we need to process only a
33713314
# _subset_ of the past SCCs instead of having to process them all.
33723315
if (
@@ -3518,16 +3461,17 @@ def process_stale_scc(graph: Graph, scc: list[str], manager: BuildManager) -> No
35183461
for id in stale:
35193462
graph[id].generate_unused_ignore_notes()
35203463
graph[id].generate_ignore_without_code_notes()
3521-
if any(manager.errors.is_errors_for_file(graph[id].xpath) for id in stale):
3522-
for id in stale:
3523-
graph[id].transitive_error = True
3464+
3465+
# Flush errors, and write cache in two phases: first data files, then meta files.
35243466
meta_tuples = {}
3467+
errors_by_id = {}
35253468
for id in stale:
35263469
if graph[id].xpath not in manager.errors.ignored_files:
35273470
errors = manager.errors.file_messages(
35283471
graph[id].xpath, formatter=manager.error_formatter
35293472
)
35303473
manager.flush_errors(manager.errors.simplify_path(graph[id].xpath), errors, False)
3474+
errors_by_id[id] = errors
35313475
meta_tuples[id] = graph[id].write_cache()
35323476
graph[id].mark_as_rechecked()
35333477
for id in stale:
@@ -3539,11 +3483,12 @@ def process_stale_scc(graph: Graph, scc: list[str], manager: BuildManager) -> No
35393483
meta["dep_hashes"] = {
35403484
dep: graph[dep].interface_hash for dep in graph[id].dependencies if dep in graph
35413485
}
3486+
meta["error_lines"] = errors_by_id.get(id, [])
35423487
graph[id].meta = write_cache_meta(meta, manager, meta_json, data_json)
35433488

35443489

35453490
def sorted_components(
3546-
graph: Graph, vertices: AbstractSet[str] | None = None, pri_max: int = PRI_ALL
3491+
graph: Graph, vertices: AbstractSet[str] | None = None, pri_max: int = PRI_INDIRECT
35473492
) -> list[AbstractSet[str]]:
35483493
"""Return the graph's SCCs, topologically sorted by dependencies.
35493494

0 commit comments

Comments
 (0)