Skip to content

Commit f694c22

Browse files
committed
Merge remote-tracking branch 'upstream/master' into batch-sccs-parallel
2 parents 35168b7 + 781f1e6 commit f694c22

34 files changed

Lines changed: 700 additions & 198 deletions

CHANGELOG.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,45 @@
22

33
## Next Release
44

5+
### Enabling `--local-partial-types` by default
6+
7+
This flag affects the inference of types based on assignments in other scopes.
8+
For now, explicitly disabling this continues to be supported, but this support will be removed
9+
in the future as the legacy behaviour is hard to support with other current and future features
10+
in mypy, like the daemon or the new implementation of flexible redefinitions.
11+
12+
Contributed by Ivan Levkivskyi, Jukka Lehtosalo, Shantanu in [PR 21163](https://github.com/python/mypy/pull/21163)
13+
14+
### Enabling `--strict-bytes` by default
15+
16+
Per [PEP 688](https://peps.python.org/pep-0688), mypy no longer treats `bytearray` and `memoryview`
17+
values as assignable to the `bytes` type.
18+
19+
Contributed by Shantanu in [PR 18371](https://github.com/python/mypy/pull/18371)
20+
521
### Drop Support for Targeting Python 3.9
622

723
Mypy no longer supports type checking code with `--python-version 3.9`.
824
Use `--python-version 3.10` or newer.
925

26+
Contributed by Shantanu, Marc Mueller in [PR 21243](https://github.com/python/mypy/pull/21243)
27+
28+
### Remove special casing of legacy bundled stubs
29+
30+
Mypy used to bundle stubs for a few packages in versions 0.812 and earlier. To navigate the
31+
transition, mypy used to report missing types for these packages even if `--ignore-missing-imports`
32+
was set. Mypy now consistently respects `--ignore-missing-imports` for all packages.
33+
34+
Contributed by Shantanu in [PR 18372](https://github.com/python/mypy/pull/18372)
35+
36+
### Prevent assignment to None for non-Optional class variables with type comments
37+
38+
Mypy used to allow assignment to None for class variables when using type comments. This was a
39+
common idiom in Python 3.5 and earlier, prior to the introduction of variable annotations.
40+
However, this was a soundness hole and has now been removed.
41+
42+
Contributed by Shantanu in [PR 20054](https://github.com/python/mypy/pull/20054)
43+
1044
## Mypy 1.20
1145

1246
We’ve just uploaded mypy 1.20.0 to the Python Package Index ([PyPI](https://pypi.org/project/mypy/)).
@@ -544,6 +578,18 @@ Please see [git log](https://github.com/python/typeshed/commits/main?after=f8f07
544578
- Allow dangerous identity comparisons to `Any` typed variables (Shantanu, PR [21142](https://github.com/python/mypy/pull/21142))
545579
- `--warn-unused-config` should not be a strict flag (Ivan Levkivskyi, PR [21139](https://github.com/python/mypy/pull/21139))
546580

581+
### Mypy 1.20.2
582+
583+
- Use WAL with SQLite cache and fix close (Shantanu, PR [21154](https://github.com/python/mypy/pull/21154))
584+
- Adjust SQLite journal mode (Ivan Levkivskyi, PR [21217](https://github.com/python/mypy/pull/21217))
585+
- Correctly aggregate narrowing information on parent expressions (Shantanu, PR [21206](https://github.com/python/mypy/pull/21206))
586+
- Fix regression related to generic callables (Shantanu, PR [21208](https://github.com/python/mypy/pull/21208))
587+
- Fix regression by avoiding widening types in some contexts (Shantanu, PR [21242](https://github.com/python/mypy/pull/21242))
588+
- Fix slicing in non-strict optional mode (Shantanu, PR [21282](https://github.com/python/mypy/pull/21282))
589+
- mypyc: Fix match statement semantics for "or" pattern (Shantanu, PR [21156](https://github.com/python/mypy/pull/21156))
590+
- mypyc: Fix issue with module dunder attributes (Piotr Sawicki, PR [21275](https://github.com/python/mypy/pull/21275))
591+
- Initial support for Python 3.15.0a8 (Marc Mueller, PR [21255](https://github.com/python/mypy/pull/21255))
592+
547593
### Acknowledgements
548594

549595
Thanks to all mypy contributors who contributed to this release:

misc/update-stubinfo.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,10 @@ def main() -> None:
3030

3131
import mypy.stubinfo
3232

33-
mypy_p = set(mypy.stubinfo.non_bundled_packages_flat) | set(
34-
mypy.stubinfo.legacy_bundled_packages
35-
)
33+
mypy_p = set(mypy.stubinfo.non_bundled_packages_flat)
3634

3735
for p in typeshed_p_to_d.keys() & mypy_p:
3836
mypy_d = mypy.stubinfo.non_bundled_packages_flat.get(p)
39-
mypy_d = mypy_d or mypy.stubinfo.legacy_bundled_packages.get(p)
4037
if mypy_d != typeshed_p_to_d[p]:
4138
raise ValueError(
4239
f"stub_distribution mismatch for {p}: {mypy_d} != {typeshed_p_to_d[p]}"

mypy/build.py

Lines changed: 83 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@
170170
from mypy.plugins.default import DefaultPlugin
171171
from mypy.renaming import LimitedVariableRenameVisitor, VariableRenameVisitor
172172
from mypy.stats import dump_type_stats
173-
from mypy.stubinfo import is_module_from_legacy_bundled_package, stub_distribution_name
173+
from mypy.stubinfo import stub_distribution_name
174174
from mypy.types import Type, instance_cache
175175
from mypy.typestate import reset_global_state, type_state
176176
from mypy.util import json_dumps, json_loads
@@ -955,7 +955,7 @@ def __init__(
955955
# until all the files have been added. This means that a
956956
# new file can be processed O(n**2) times. This cache
957957
# avoids most of this redundant work.
958-
self.ast_cache: dict[str, tuple[MypyFile, list[ErrorInfo]]] = {}
958+
self.ast_cache: dict[str, tuple[MypyFile, list[ErrorInfo], str | None]] = {}
959959
# Number of times we used GC optimization hack for fresh SCCs.
960960
self.gc_freeze_cycles = 0
961961
# Mapping from SCC id to corresponding SCC instance. This is populated
@@ -1043,11 +1043,68 @@ def parse_parallel(self, sequential_states: list[State], parallel_states: list[S
10431043
as an optimization to parallelize only those parts of the code that can be
10441044
parallelized efficiently.
10451045
"""
1046+
parallel_parsed_states, parallel_parsed_states_set = self.parse_files_threaded_raw(
1047+
sequential_states, parallel_states
1048+
)
1049+
1050+
for state in parallel_parsed_states:
1051+
# New parser returns serialized ASTs. Deserialize full trees only if not using
1052+
# parallel workers.
1053+
with state.wrap_context():
1054+
assert state.tree is not None
1055+
raw_data = state.tree.raw_data
1056+
if raw_data is not None:
1057+
# Apply inline mypy config before deserialization, since
1058+
# some options (e.g. implicit_optional) affect deserialization
1059+
state.source_hash = raw_data.source_hash
1060+
state.apply_inline_configuration(raw_data.mypy_comments)
1061+
state.tree = load_from_raw(
1062+
state.xpath,
1063+
state.id,
1064+
raw_data,
1065+
self.errors,
1066+
state.options,
1067+
imports_only=bool(self.workers),
1068+
)
1069+
if self.errors.is_blockers():
1070+
self.log("Bailing due to parse errors")
1071+
self.errors.raise_error()
1072+
1073+
for state in parallel_states:
1074+
assert state.tree is not None
1075+
if state in parallel_parsed_states_set:
1076+
if state.tree.raw_data is not None:
1077+
# source_hash was already extracted above, but raw_data
1078+
# may have been preserved for workers (imports_only=True).
1079+
pass
1080+
elif state.source_hash is None:
1081+
# At least namespace packages may not have source.
1082+
state.get_source()
1083+
state.size_hint = os.path.getsize(state.xpath)
1084+
state.early_errors = list(self.errors.error_info_map.get(state.xpath, []))
1085+
state.semantic_analysis_pass1()
1086+
self.ast_cache[state.id] = (state.tree, state.early_errors, state.source_hash)
1087+
self.modules[state.id] = state.tree
1088+
if state.tree.raw_data is not None:
1089+
state.size_hint = len(state.tree.raw_data.defs) + MIN_SIZE_HINT
1090+
state.check_blockers()
1091+
state.setup_errors()
1092+
1093+
def parse_files_threaded_raw(
1094+
self, sequential_states: list[State], parallel_states: list[State]
1095+
) -> tuple[list[State], set[State]]:
1096+
"""Parse files using a thread pool.
1097+
1098+
Also parse sequential states while waiting for the parallel results.
1099+
Trees from the new parser are left in raw (serialized) form.
1100+
1101+
Return (list, set) of states that were actually parsed (not cached).
1102+
"""
10461103
futures = []
10471104
# Use both list and a set to have more predictable order of errors,
10481105
# while also not sacrificing performance.
1049-
parallel_parsed_states = []
1050-
parallel_parsed_states_set = set()
1106+
parallel_parsed_states: list[State] = []
1107+
parallel_parsed_states_set: set[State] = set()
10511108
# Use at least --num-workers if specified by user.
10521109
available_threads = max(get_available_threads(), self.options.num_workers)
10531110
# Overhead from trying to parallelize (small) blocking portion of
@@ -1056,56 +1113,27 @@ def parse_parallel(self, sequential_states: list[State], parallel_states: list[S
10561113
with ThreadPoolExecutor(max_workers=min(available_threads, 8)) as executor:
10571114
for state in parallel_states:
10581115
state.needs_parse = False
1059-
# New parser reads source from file directly, we do this only for
1060-
# the side effect of parsing inline mypy configurations.
1061-
state.get_source()
10621116
if state.id not in self.ast_cache:
10631117
self.log(f"Parsing {state.xpath} ({state.id})")
10641118
ignore_errors = state.ignore_all or state.options.ignore_errors
10651119
if ignore_errors:
10661120
self.errors.ignored_files.add(state.xpath)
1067-
futures.append(executor.submit(state.parse_file_inner, state.source or ""))
1121+
futures.append(executor.submit(state.parse_file_inner, ""))
10681122
parallel_parsed_states.append(state)
10691123
parallel_parsed_states_set.add(state)
10701124
else:
10711125
self.log(f"Using cached AST for {state.xpath} ({state.id})")
1072-
state.tree, state.early_errors = self.ast_cache[state.id]
1126+
state.tree, state.early_errors, source_hash = self.ast_cache[state.id]
1127+
state.source_hash = source_hash
10731128

10741129
# Parse sequential before waiting on parallel.
10751130
for state in sequential_states:
10761131
state.parse_file()
10771132

10781133
for fut in wait(futures).done:
10791134
fut.result()
1080-
for state in parallel_parsed_states:
1081-
# New parser returns serialized trees that need to be de-serialized.
1082-
with state.wrap_context():
1083-
assert state.tree is not None
1084-
if state.tree.raw_data:
1085-
state.tree = load_from_raw(
1086-
state.xpath,
1087-
state.id,
1088-
state.tree.raw_data,
1089-
self.errors,
1090-
state.options,
1091-
imports_only=bool(self.workers),
1092-
)
1093-
if self.errors.is_blockers():
1094-
self.log("Bailing due to parse errors")
1095-
self.errors.raise_error()
10961135

1097-
for state in parallel_states:
1098-
assert state.tree is not None
1099-
if state in parallel_parsed_states_set:
1100-
state.early_errors = list(self.errors.error_info_map.get(state.xpath, []))
1101-
state.semantic_analysis_pass1()
1102-
self.ast_cache[state.id] = (state.tree, state.early_errors)
1103-
self.modules[state.id] = state.tree
1104-
assert state.tree is not None
1105-
if state.tree.raw_data is not None:
1106-
state.size_hint = len(state.tree.raw_data.defs) + MIN_SIZE_HINT
1107-
state.check_blockers()
1108-
state.setup_errors()
1136+
return parallel_parsed_states, parallel_parsed_states_set
11091137

11101138
def post_parse_all(self, states: list[State]) -> None:
11111139
for state in states:
@@ -3134,7 +3162,6 @@ def get_source(self) -> str:
31343162
self.source_hash = compute_hash(source)
31353163

31363164
self.parse_inline_configuration(source)
3137-
self.check_for_invalid_options()
31383165

31393166
self.size_hint = len(source) + MIN_SIZE_HINT
31403167
self.time_spent_us += time_spent_us(t0)
@@ -3159,7 +3186,10 @@ def parse_file(self, *, temporary: bool = False, raw_data: FileRawData | None =
31593186
# The file was already parsed.
31603187
return
31613188

3162-
source = self.get_source()
3189+
if raw_data is None:
3190+
source = self.get_source()
3191+
else:
3192+
source = ""
31633193
manager = self.manager
31643194
# Can we reuse a previously parsed AST? This avoids redundant work in daemon.
31653195
if self.id not in manager.ast_cache:
@@ -3169,6 +3199,12 @@ def parse_file(self, *, temporary: bool = False, raw_data: FileRawData | None =
31693199
self.manager.errors.ignored_files.add(self.xpath)
31703200
with self.wrap_context():
31713201
manager.errors.set_file(self.xpath, self.id, options=self.options)
3202+
if raw_data is not None:
3203+
# Apply inline mypy config before deserialization, since
3204+
# some options (e.g. implicit_optional) affect how the
3205+
# AST is built during deserialization.
3206+
self.source_hash = raw_data.source_hash
3207+
self.apply_inline_configuration(raw_data.mypy_comments)
31723208
self.parse_file_inner(source, raw_data)
31733209
assert self.tree is not None
31743210
# New parser returns serialized trees that need to be de-serialized.
@@ -3193,14 +3229,15 @@ def parse_file(self, *, temporary: bool = False, raw_data: FileRawData | None =
31933229
else:
31943230
# Reuse a cached AST
31953231
manager.log(f"Using cached AST for {self.xpath} ({self.id})")
3196-
self.tree, self.early_errors = manager.ast_cache[self.id]
3232+
self.tree, self.early_errors, source_hash = manager.ast_cache[self.id]
3233+
self.source_hash = source_hash
31973234

31983235
assert self.tree is not None
31993236
if not temporary:
32003237
manager.modules[self.id] = self.tree
32013238
self.check_blockers()
32023239

3203-
manager.ast_cache[self.id] = (self.tree, self.early_errors)
3240+
manager.ast_cache[self.id] = (self.tree, self.early_errors, self.source_hash)
32043241
assert self.tree is not None
32053242
if self.tree.raw_data is not None:
32063243
# Size of serialized tree is a better proxy for file complexity than
@@ -3221,12 +3258,17 @@ def setup_errors(self) -> None:
32213258
def parse_inline_configuration(self, source: str) -> None:
32223259
"""Check for inline mypy: options directive and parse them."""
32233260
flags = get_mypy_comments(source)
3261+
self.apply_inline_configuration(flags)
3262+
3263+
def apply_inline_configuration(self, flags: list[tuple[int, str]] | None) -> None:
3264+
"""Apply inline mypy configuration comments and check for invalid options."""
32243265
if flags:
32253266
changes, config_errors = parse_mypy_comments(flags, self.options)
32263267
self.options = self.options.apply_changes(changes)
32273268
self.manager.errors.set_file(self.xpath, self.id, self.options)
32283269
for lineno, error in config_errors:
32293270
self.manager.error(lineno, error)
3271+
self.check_for_invalid_options()
32303272

32313273
def check_for_invalid_options(self) -> None:
32323274
if self.options.mypyc and not self.options.strict_bytes:
@@ -3714,19 +3756,6 @@ def find_module_and_diagnose(
37143756

37153757
ignore_missing_imports = options.ignore_missing_imports
37163758

3717-
# Don't honor a global (not per-module) ignore_missing_imports
3718-
# setting for modules that used to have bundled stubs, as
3719-
# otherwise updating mypy can silently result in new false
3720-
# negatives. (Unless there are stubs, but they are incomplete.)
3721-
global_ignore_missing_imports = manager.options.ignore_missing_imports
3722-
if (
3723-
is_module_from_legacy_bundled_package(id)
3724-
and global_ignore_missing_imports
3725-
and not options.ignore_missing_imports_per_module
3726-
and result is ModuleNotFoundReason.APPROVED_STUBS_NOT_INSTALLED
3727-
):
3728-
ignore_missing_imports = False
3729-
37303759
if skip_diagnose:
37313760
raise ModuleNotFound
37323761
if caller_state:

0 commit comments

Comments
 (0)