Skip to content

Commit 330dce6

Browse files
Merge branch 'master' into patch-6
2 parents 328a11d + 4301be1 commit 330dce6

File tree

23 files changed

+507
-346
lines changed

23 files changed

+507
-346
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'

CHANGELOG.md

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

mypy/build.py

Lines changed: 18 additions & 73 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,6 +3483,7 @@ 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

mypy/main.py

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1163,22 +1163,22 @@ def add_invertible_flag(
11631163
"--skip-c-gen", dest="mypyc_skip_c_generation", action="store_true", help=argparse.SUPPRESS
11641164
)
11651165

1166-
other_group = parser.add_argument_group(title="Miscellaneous")
1167-
other_group.add_argument("--quickstart-file", help=argparse.SUPPRESS)
1168-
other_group.add_argument("--junit-xml", help="Write junit.xml to the given file")
1166+
misc_group = parser.add_argument_group(title="Miscellaneous")
1167+
misc_group.add_argument("--quickstart-file", help=argparse.SUPPRESS)
1168+
misc_group.add_argument("--junit-xml", help="Write junit.xml to the given file")
11691169
imports_group.add_argument(
11701170
"--junit-format",
11711171
choices=["global", "per_file"],
11721172
default="global",
11731173
help="If --junit-xml is set, specifies format. global: single test with all errors; per_file: one test entry per file with failures",
11741174
)
1175-
other_group.add_argument(
1175+
misc_group.add_argument(
11761176
"--find-occurrences",
11771177
metavar="CLASS.MEMBER",
11781178
dest="special-opts:find_occurrences",
11791179
help="Print out all usages of a class member (experimental)",
11801180
)
1181-
other_group.add_argument(
1181+
misc_group.add_argument(
11821182
"--scripts-are-modules",
11831183
action="store_true",
11841184
help="Script x becomes module x instead of __main__",
@@ -1189,7 +1189,7 @@ def add_invertible_flag(
11891189
default=False,
11901190
strict_flag=False,
11911191
help="Install detected missing library stub packages using pip",
1192-
group=other_group,
1192+
group=misc_group,
11931193
)
11941194
add_invertible_flag(
11951195
"--non-interactive",
@@ -1199,19 +1199,12 @@ def add_invertible_flag(
11991199
"Install stubs without asking for confirmation and hide "
12001200
+ "errors, with --install-types"
12011201
),
1202-
group=other_group,
1202+
group=misc_group,
12031203
inverse="--interactive",
12041204
)
12051205

12061206
if server_options:
1207-
# TODO: This flag is superfluous; remove after a short transition (2018-03-16)
1208-
other_group.add_argument(
1209-
"--experimental",
1210-
action="store_true",
1211-
dest="fine_grained_incremental",
1212-
help="Enable fine-grained incremental mode",
1213-
)
1214-
other_group.add_argument(
1207+
misc_group.add_argument(
12151208
"--use-fine-grained-cache",
12161209
action="store_true",
12171210
help="Use the cache in fine-grained incremental mode",

mypy/stubtest.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,7 +1053,10 @@ def get_kind(arg_name: str) -> nodes.ArgKind:
10531053

10541054

10551055
def _verify_signature(
1056-
stub: Signature[nodes.Argument], runtime: Signature[inspect.Parameter], function_name: str
1056+
stub: Signature[nodes.Argument],
1057+
runtime: Signature[inspect.Parameter],
1058+
function_name: str,
1059+
warn_runtime_is_object_init: bool = False,
10571060
) -> Iterator[str]:
10581061
# Check positional arguments match up
10591062
for stub_arg, runtime_arg in zip(stub.pos, runtime.pos):
@@ -1098,6 +1101,8 @@ def _verify_signature(
10981101
msg = f'runtime does not have parameter "{stub_arg.variable.name}"'
10991102
if runtime.varkw is not None:
11001103
msg += ". Maybe you forgot to make it keyword-only in the stub?"
1104+
elif warn_runtime_is_object_init:
1105+
msg += ". You may need to write stubs for __new__ instead of __init__."
11011106
yield msg
11021107
else:
11031108
yield f'stub parameter "{stub_arg.variable.name}" is not keyword-only'
@@ -1137,7 +1142,11 @@ def _verify_signature(
11371142
if arg not in {runtime_arg.name for runtime_arg in runtime.pos[len(stub.pos) :]}:
11381143
yield f'runtime parameter "{arg}" is not keyword-only'
11391144
else:
1140-
yield f'runtime does not have parameter "{arg}"'
1145+
msg = f'runtime does not have parameter "{arg}"'
1146+
if warn_runtime_is_object_init:
1147+
msg += ". You may need to write stubs for __new__ instead of __init__."
1148+
yield msg
1149+
11411150
for arg in sorted(set(runtime.kwonly) - set(stub.kwonly)):
11421151
if arg in {stub_arg.variable.name for stub_arg in stub.pos}:
11431152
# Don't report this if we've reported it before
@@ -1223,7 +1232,12 @@ def verify_funcitem(
12231232
if not signature:
12241233
return
12251234

1226-
for message in _verify_signature(stub_sig, runtime_sig, function_name=stub.name):
1235+
for message in _verify_signature(
1236+
stub_sig,
1237+
runtime_sig,
1238+
function_name=stub.name,
1239+
warn_runtime_is_object_init=runtime is object.__init__,
1240+
):
12271241
yield Error(
12281242
object_path,
12291243
"is inconsistent, " + message,
@@ -1333,7 +1347,12 @@ def verify_overloadedfuncdef(
13331347
stub_sig = Signature.from_overloadedfuncdef(stub)
13341348
runtime_sig = Signature.from_inspect_signature(signature)
13351349

1336-
for message in _verify_signature(stub_sig, runtime_sig, function_name=stub.name):
1350+
for message in _verify_signature(
1351+
stub_sig,
1352+
runtime_sig,
1353+
function_name=stub.name,
1354+
warn_runtime_is_object_init=runtime is object.__init__,
1355+
):
13371356
# TODO: This is a little hacky, but the addition here is super useful
13381357
if "has a default value of type" in message:
13391358
message += (

0 commit comments

Comments
 (0)