Skip to content

Commit 3dc8d57

Browse files
committed
Be more careful with caching local partial types
1 parent 058f4ca commit 3dc8d57

6 files changed

Lines changed: 84 additions & 11 deletions

File tree

‎mypy/build.py‎

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -898,7 +898,6 @@ def __init__(
898898
self.errors,
899899
self.plugin,
900900
self.import_map,
901-
parallel_worker,
902901
)
903902
self.all_types: dict[Expression, Type] = {} # Enabled by export_types
904903
self.indirection_detector = TypeIndirectionVisitor()
@@ -2056,14 +2055,26 @@ def find_cache_meta(
20562055

20572056
# Ignore cache if (relevant) options aren't the same.
20582057
# Note that it's fine to mutilate cached_options since it's only used here.
2059-
cached_options = m.options
2058+
cached_options = m.options.copy()
20602059
current_options = options_snapshot(id, manager)
20612060
if manager.options.skip_version_check:
20622061
# When we're lax about version we're also lax about platform.
20632062
cached_options["platform"] = current_options["platform"]
20642063
if "debug_cache" in cached_options:
20652064
# Older versions included debug_cache, but it's silly to compare it.
20662065
del cached_options["debug_cache"]
2066+
if "local_partial_types" in cached_options:
2067+
local_partial_types = cached_options["local_partial_types"]
2068+
del cached_options["local_partial_types"]
2069+
is_parallel = manager.options.num_workers > 0
2070+
if not local_partial_types and cached_options["is_parallel"] != is_parallel:
2071+
# If local partial types are disabled, behavior is too different for
2072+
# parallel and sequential runs, see write_cache for details.
2073+
return None
2074+
del cached_options["is_parallel"]
2075+
else:
2076+
# Cache from an old mypy version.
2077+
return None
20672078
if cached_options != current_options:
20682079
manager.log(f"Metadata abandoned for {id}: options differ")
20692080
if manager.options.verbosity >= 2:
@@ -2227,7 +2238,6 @@ def validate_meta(
22272238
meta.mtime = mtime
22282239
meta.path = path
22292240
meta.size = size
2230-
meta.options = options_snapshot(id, manager)
22312241
meta_file, _, _ = get_cache_names(id, path, manager.options)
22322242
if manager.logging_enabled:
22332243
manager.log(
@@ -2272,6 +2282,8 @@ def write_cache(
22722282
trans_dep_hash: bytes,
22732283
source_hash: str,
22742284
ignore_all: bool,
2285+
local_partial_types: bool,
2286+
is_parallel: bool,
22752287
manager: BuildManager,
22762288
) -> tuple[bytes, tuple[CacheMeta, str] | None]:
22772289
"""Write cache files for a module.
@@ -2371,6 +2383,16 @@ def write_cache(
23712383
# important, or otherwise the options would never match when
23722384
# verifying the cache.
23732385
assert source_hash is not None
2386+
# Local partial types need special handling as they behave differently
2387+
# in sequential and parallel runs:
2388+
# * In sequential run we respected the option, but use different SCC
2389+
# processing logic depending on whether they are enabled or disabled.
2390+
# * In parallel run they are always on, and we give an error if a user
2391+
# tries to disable them.
2392+
extra_options = {
2393+
"local_partial_types": local_partial_types,
2394+
"is_parallel": is_parallel,
2395+
}
23742396
meta = CacheMeta(
23752397
id=id,
23762398
path=path,
@@ -2382,7 +2404,7 @@ def write_cache(
23822404
data_file=data_file,
23832405
suppressed=suppressed,
23842406
imports_ignored=imports_ignored,
2385-
options=options_snapshot(id, manager),
2407+
options=options_snapshot(id, manager) | extra_options,
23862408
suppressed_deps_opts=suppressed_deps_opts,
23872409
dep_prios=dep_prios,
23882410
dep_lines=dep_lines,
@@ -2727,6 +2749,11 @@ def new_state(
27272749
meta, meta_ex = meta_pair
27282750
interface_hash = meta.interface_hash
27292751
meta_source_hash = meta.hash
2752+
# Update the local partial types in case they were set by an inline config.
2753+
# So we can select the correct SCC processing logic without reading the file.
2754+
local_partial_types = meta.options["local_partial_types"]
2755+
if options.local_partial_types != local_partial_types:
2756+
options = options.apply_changes({"local_partial_types": local_partial_types})
27302757
if path and source is None and manager.fscache.isdir(path):
27312758
source = ""
27322759

@@ -3637,6 +3664,8 @@ def write_cache(self) -> tuple[CacheMeta, str] | None:
36373664
self.trans_dep_hash,
36383665
self.source_hash,
36393666
self.ignore_all,
3667+
self.options.local_partial_types,
3668+
self.options.num_workers > 0,
36403669
self.manager,
36413670
)
36423671
if new_interface_hash == self.interface_hash:

‎mypy/semanal.py‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,6 @@ def __init__(
461461
errors: Errors,
462462
plugin: Plugin,
463463
import_map: dict[str, set[str]],
464-
parallel_worker: bool,
465464
) -> None:
466465
"""Construct semantic analyzer.
467466
@@ -495,8 +494,6 @@ def __init__(
495494
self.errors = errors
496495
self.modules = modules
497496
self.import_map = import_map
498-
# True if this analysis is run in a parallel worker process.
499-
self.parallel_worker = parallel_worker
500497
self.msg = MessageBuilder(errors, modules)
501498
self.missing_modules = missing_modules
502499
self.missing_names = [set()]
@@ -740,7 +737,7 @@ def refresh_top_level(self, file_node: MypyFile) -> None:
740737
self.ad_hoc_error(
741738
"--allow-redefinition-old and --allow-redefinition should not be used together"
742739
)
743-
if not self.options.local_partial_types and self.parallel_worker:
740+
if not self.options.local_partial_types and self.options.num_workers > 0:
744741
self.ad_hoc_error("--local-partial-types must be enabled in parallel mode")
745742
self.recurse_into_functions = False
746743
self.add_implicit_module_attrs(file_node)

‎mypy/test/testcmdline.py‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def test_python_cmdline(testcase: DataDrivenTestCase, step: int) -> None:
5858
with open(program_path, "w", encoding="utf8") as file:
5959
for s in testcase.input:
6060
file.write(f"{s}\n")
61-
args = parse_args(normalize_devnull(testcase.input[0]))
61+
args = parse_args(normalize_devnull(testcase.input[step - 1]), step)
6262
custom_cwd = parse_cwd(testcase.input[1]) if len(testcase.input) > 1 else None
6363
args.append("--show-traceback")
6464
if "--error-summary" not in args:
@@ -122,7 +122,7 @@ def test_python_cmdline(testcase: DataDrivenTestCase, step: int) -> None:
122122
)
123123

124124

125-
def parse_args(line: str) -> list[str]:
125+
def parse_args(line: str, step: int) -> list[str]:
126126
"""Parse the first line of the program for the command line.
127127
128128
This should have the form
@@ -133,7 +133,8 @@ def parse_args(line: str) -> list[str]:
133133
134134
# cmd: mypy pkg/
135135
"""
136-
m = re.match("# cmd: mypy (.*)$", line)
136+
step_str = "" if step == 1 else str(step)
137+
m = re.match(f"# cmd{step_str}: mypy (.*)$", line)
137138
if not m:
138139
return [] # No args; mypy will spit out an error.
139140
return shlex.split(m.group(1))

‎test-data/unit/check-incremental.test‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8251,3 +8251,27 @@ y = 2
82518251
[stale]
82528252
[out2]
82538253
[out3]
8254+
8255+
[case testNoLocalPartialTypesInlineRespectedIncremental_no_parallel]
8256+
import a
8257+
[file a.py]
8258+
# mypy: no-local-partial-types
8259+
import dep
8260+
8261+
items = []
8262+
8263+
def add() -> None:
8264+
items.append(1)
8265+
8266+
reveal_type(items)
8267+
[file dep.py]
8268+
value: int = 1
8269+
[file dep.py.2]
8270+
value: str = "x"
8271+
[builtins fixtures/list.pyi]
8272+
[rechecked a, dep]
8273+
[stale dep]
8274+
[out]
8275+
tmp/a.py:9: note: Revealed type is "builtins.list[builtins.int]"
8276+
[out2]
8277+
tmp/a.py:9: note: Revealed type is "builtins.list[builtins.int]"

‎test-data/unit/cmdline.test‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1330,3 +1330,23 @@ error: Cache must be enabled in parallel mode
13301330
from foo.api import bar as bar
13311331
[file foo-stubs/api/bar.pyi]
13321332
[out]
1333+
1334+
[case testCacheInvalidatedForNoLocalPartialTypesParallelVsSequential]
1335+
# cmd: mypy a.py
1336+
# cmd2: mypy a.py --num-workers=2
1337+
[file a.py]
1338+
# mypy: no-local-partial-types
1339+
1340+
items = []
1341+
1342+
def add() -> None:
1343+
items.append(1)
1344+
1345+
reveal_type(items)
1346+
[out]
1347+
a.py:8: note: Revealed type is "list[int]"
1348+
== Return code: 0
1349+
[out2]
1350+
a.py:1: error: --local-partial-types must be enabled in parallel mode
1351+
a.py:3: error: Need type annotation for "items" (hint: "items: list[<type>] = ...")
1352+
a.py:8: note: Revealed type is "list[Any]"

‎test-data/unit/exportjson.test‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,8 @@ from typing_extensions import Final
298298
],
299299
"suppressed": [],
300300
"options": {
301+
"is_parallel": false,
302+
"local_partial_types": true,
301303
"other_options": "<hash>",
302304
"platform": ...
303305
},

0 commit comments

Comments
 (0)