Skip to content

Commit 4581296

Browse files
authored
Merge branch 'master' into fix/add-pre_commit
2 parents cf57f72 + c32d11e commit 4581296

File tree

17 files changed

+637
-96
lines changed

17 files changed

+637
-96
lines changed

.github/workflows/test.yml

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,14 @@ jobs:
7070
toxenv: py
7171
tox_extra_args: "-n 4"
7272
test_mypyc: true
73-
7473
- name: Test suite with py313-ubuntu, mypyc-compiled
7574
python: '3.13'
7675
arch: x64
7776
os: ubuntu-latest
7877
toxenv: py
7978
tox_extra_args: "-n 4"
8079
test_mypyc: true
80+
8181
# - name: Test suite with py314-dev-ubuntu
8282
# python: '3.14-dev'
8383
# arch: x64
@@ -94,13 +94,16 @@ jobs:
9494
os: macos-13
9595
toxenv: py
9696
tox_extra_args: "-n 3 mypyc/test/test_run.py mypyc/test/test_external.py"
97-
- name: mypyc runtime tests with py38-debug-build-ubuntu
98-
python: '3.8.17'
99-
arch: x64
100-
os: ubuntu-latest
101-
toxenv: py
102-
tox_extra_args: "-n 4 mypyc/test/test_run.py mypyc/test/test_external.py"
103-
debug_build: true
97+
# This is broken. See
98+
# - https://github.com/python/mypy/issues/17819
99+
# - https://github.com/python/mypy/pull/17822
100+
# - name: mypyc runtime tests with py38-debug-build-ubuntu
101+
# python: '3.8.17'
102+
# arch: x64
103+
# os: ubuntu-latest
104+
# toxenv: py
105+
# tox_extra_args: "-n 4 mypyc/test/test_run.py mypyc/test/test_external.py"
106+
# debug_build: true
104107

105108
- name: Type check our own code (py38-ubuntu)
106109
python: '3.8'
@@ -148,17 +151,17 @@ jobs:
148151
./misc/build-debug-python.sh $PYTHONVERSION $PYTHONDIR $VENV
149152
# TODO: does this do anything? env vars aren't passed to the next step right
150153
source $VENV/bin/activate
151-
- name: Latest Dev build
154+
- name: Latest dev build
152155
if: ${{ endsWith(matrix.python, '-dev') }}
153156
run: |
154-
sudo apt-get update
155-
sudo apt-get install -y --no-install-recommends \
156-
build-essential gdb lcov libbz2-dev libffi-dev libgdbm-dev liblzma-dev libncurses5-dev \
157-
libreadline6-dev libsqlite3-dev libssl-dev lzma lzma-dev tk-dev uuid-dev zlib1g-dev
158157
git clone --depth 1 https://github.com/python/cpython.git /tmp/cpython --branch $( echo ${{ matrix.python }} | sed 's/-dev//' )
159158
cd /tmp/cpython
160159
echo git rev-parse HEAD; git rev-parse HEAD
161160
git show --no-patch
161+
sudo apt-get update
162+
sudo apt-get install -y --no-install-recommends \
163+
build-essential gdb lcov libbz2-dev libffi-dev libgdbm-dev liblzma-dev libncurses5-dev \
164+
libreadline6-dev libsqlite3-dev libssl-dev lzma lzma-dev tk-dev uuid-dev zlib1g-dev
162165
./configure --prefix=/opt/pythondev
163166
make -j$(nproc)
164167
sudo make install
@@ -190,7 +193,7 @@ jobs:
190193
191194
- name: Setup tox environment
192195
run: |
193-
tox run -e ${{ matrix.toxenv }} --notes
196+
tox run -e ${{ matrix.toxenv }} --notest
194197
- name: Test
195198
run: tox run -e ${{ matrix.toxenv }} --skip-pkg-install -- ${{ matrix.tox_extra_args }}
196199
continue-on-error: ${{ matrix.allow_failure == 'true' }}

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,8 @@ List of documentation updates:
187187
* Make changelog visible in mypy documentation (quinn-sasha, PR [17742](https://github.com/python/mypy/pull/17742))
188188
* List all incomplete features in `--enable-incomplete-feature` docs (sobolevn, PR [17633](https://github.com/python/mypy/pull/17633))
189189
* Remove the explicit setting of a pygments theme (Pradyun Gedam, PR [17571](https://github.com/python/mypy/pull/17571))
190+
* Document ReadOnly with TypedDict (Jukka Lehtosalo, PR [17905](https://github.com/python/mypy/pull/17905))
191+
* Document TypeIs (Chelsea Durazo, PR [17821](https://github.com/python/mypy/pull/17821))
190192

191193
### Experimental Inline TypedDict Syntax
192194

@@ -250,6 +252,9 @@ This feature was contributed by Ivan Levkivskyi (PR [17457](https://github.com/p
250252
* Fix typechecking for async generators (Danny Yang, PR [17452](https://github.com/python/mypy/pull/17452))
251253
* Fix strict optional handling in attrs plugin (Ivan Levkivskyi, PR [17451](https://github.com/python/mypy/pull/17451))
252254
* Allow mixing ParamSpec and TypeVarTuple in Generic (Ivan Levkivskyi, PR [17450](https://github.com/python/mypy/pull/17450))
255+
* Improvements to `functools.partial` of types (Shantanu, PR [17898](https://github.com/python/mypy/pull/17898))
256+
* Make ReadOnly TypedDict items covariant (Jukka Lehtosalo, PR [17904](https://github.com/python/mypy/pull/17904))
257+
* Fix union callees with `functools.partial` (Jukka Lehtosalo, PR [17903](https://github.com/python/mypy/pull/17903))
253258

254259
### Typeshed Updates
255260

@@ -263,6 +268,7 @@ Thanks to all mypy contributors who contributed to this release:
263268
- Bénédikt Tran
264269
- Brian Schubert
265270
- bzoracler
271+
- Chelsea Durazo
266272
- Danny Yang
267273
- Edgar Ramírez Mondragón
268274
- Eric Mark Martin

mypy/build.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -736,8 +736,8 @@ def maybe_swap_for_shadow_path(self, path: str) -> str:
736736
shadow_file = self.shadow_equivalence_map.get(path)
737737
return shadow_file if shadow_file else path
738738

739-
def get_stat(self, path: str) -> os.stat_result:
740-
return self.fscache.stat(self.maybe_swap_for_shadow_path(path))
739+
def get_stat(self, path: str) -> os.stat_result | None:
740+
return self.fscache.stat_or_none(self.maybe_swap_for_shadow_path(path))
741741

742742
def getmtime(self, path: str) -> int:
743743
"""Return a file's mtime; but 0 in bazel mode.
@@ -1394,9 +1394,9 @@ def validate_meta(
13941394
if bazel:
13951395
# Normalize path under bazel to make sure it isn't absolute
13961396
path = normpath(path, manager.options)
1397-
try:
1398-
st = manager.get_stat(path)
1399-
except OSError:
1397+
1398+
st = manager.get_stat(path)
1399+
if st is None:
14001400
return None
14011401
if not stat.S_ISDIR(st.st_mode) and not stat.S_ISREG(st.st_mode):
14021402
manager.log(f"Metadata abandoned for {id}: file or directory {path} does not exist")
@@ -1572,10 +1572,9 @@ def write_cache(
15721572
plugin_data = manager.plugin.report_config_data(ReportConfigContext(id, path, is_check=False))
15731573

15741574
# Obtain and set up metadata
1575-
try:
1576-
st = manager.get_stat(path)
1577-
except OSError as err:
1578-
manager.log(f"Cannot get stat for {path}: {err}")
1575+
st = manager.get_stat(path)
1576+
if st is None:
1577+
manager.log(f"Cannot get stat for {path}")
15791578
# Remove apparently-invalid cache files.
15801579
# (This is purely an optimization.)
15811580
for filename in [data_json, meta_json]:

mypy/checker.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,18 @@ class PartialTypeScope(NamedTuple):
287287
is_local: bool
288288

289289

290+
class InstanceDeprecatedVisitor(TypeTraverserVisitor):
291+
"""Visitor that recursively checks for deprecations in nested instances."""
292+
293+
def __init__(self, typechecker: TypeChecker, context: Context) -> None:
294+
self.typechecker = typechecker
295+
self.context = context
296+
297+
def visit_instance(self, t: Instance) -> None:
298+
super().visit_instance(t)
299+
self.typechecker.check_deprecated(t.type, self.context)
300+
301+
290302
class TypeChecker(NodeVisitor[None], CheckerPluginInterface):
291303
"""Mypy type checker.
292304
@@ -2930,14 +2942,14 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None:
29302942
Handle all kinds of assignment statements (simple, indexed, multiple).
29312943
"""
29322944

2933-
if isinstance(s.rvalue, TempNode) and s.rvalue.no_rhs:
2945+
if s.unanalyzed_type is not None:
29342946
for lvalue in s.lvalues:
29352947
if (
29362948
isinstance(lvalue, NameExpr)
29372949
and isinstance(var := lvalue.node, Var)
2938-
and isinstance(instance := get_proper_type(var.type), Instance)
2950+
and (var.type is not None)
29392951
):
2940-
self.check_deprecated(instance.type, s)
2952+
var.type.accept(InstanceDeprecatedVisitor(typechecker=self, context=s))
29412953

29422954
# Avoid type checking type aliases in stubs to avoid false
29432955
# positives about modern type syntax available in stubs such

mypy/errors.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -922,9 +922,25 @@ def file_messages(self, path: str, formatter: ErrorFormatter | None = None) -> l
922922
self.flushed_files.add(path)
923923
source_lines = None
924924
if self.options.pretty and self.read_source:
925-
source_lines = self.read_source(path)
925+
# Find shadow file mapping and read source lines if a shadow file exists for the given path.
926+
# If shadow file mapping is not found, read source lines
927+
mapped_path = self.find_shadow_file_mapping(path)
928+
if mapped_path:
929+
source_lines = self.read_source(mapped_path)
930+
else:
931+
source_lines = self.read_source(path)
926932
return self.format_messages(error_tuples, source_lines)
927933

934+
def find_shadow_file_mapping(self, path: str) -> str | None:
935+
"""Return the shadow file path for a given source file path or None."""
936+
if self.options.shadow_file is None:
937+
return None
938+
939+
for i in self.options.shadow_file:
940+
if i[0] == path:
941+
return i[1]
942+
return None
943+
928944
def new_messages(self) -> list[str]:
929945
"""Return a string list of new error messages.
930946

mypy/fscache.py

Lines changed: 26 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ def set_package_root(self, package_root: list[str]) -> None:
5151

5252
def flush(self) -> None:
5353
"""Start another transaction and empty all caches."""
54-
self.stat_cache: dict[str, os.stat_result] = {}
55-
self.stat_error_cache: dict[str, OSError] = {}
54+
self.stat_or_none_cache: dict[str, os.stat_result | None] = {}
55+
5656
self.listdir_cache: dict[str, list[str]] = {}
5757
self.listdir_error_cache: dict[str, OSError] = {}
5858
self.isfile_case_cache: dict[str, bool] = {}
@@ -62,24 +62,21 @@ def flush(self) -> None:
6262
self.hash_cache: dict[str, str] = {}
6363
self.fake_package_cache: set[str] = set()
6464

65-
def stat(self, path: str) -> os.stat_result:
66-
if path in self.stat_cache:
67-
return self.stat_cache[path]
68-
if path in self.stat_error_cache:
69-
raise copy_os_error(self.stat_error_cache[path])
65+
def stat_or_none(self, path: str) -> os.stat_result | None:
66+
if path in self.stat_or_none_cache:
67+
return self.stat_or_none_cache[path]
68+
69+
st = None
7070
try:
7171
st = os.stat(path)
72-
except OSError as err:
72+
except OSError:
7373
if self.init_under_package_root(path):
7474
try:
75-
return self._fake_init(path)
75+
st = self._fake_init(path)
7676
except OSError:
7777
pass
78-
# Take a copy to get rid of associated traceback and frame objects.
79-
# Just assigning to __traceback__ doesn't free them.
80-
self.stat_error_cache[path] = copy_os_error(err)
81-
raise err
82-
self.stat_cache[path] = st
78+
79+
self.stat_or_none_cache[path] = st
8380
return st
8481

8582
def init_under_package_root(self, path: str) -> bool:
@@ -112,9 +109,9 @@ def init_under_package_root(self, path: str) -> bool:
112109
if not os.path.basename(dirname).isidentifier():
113110
# Can't put an __init__.py in a place that's not an identifier
114111
return False
115-
try:
116-
st = self.stat(dirname)
117-
except OSError:
112+
113+
st = self.stat_or_none(dirname)
114+
if st is None:
118115
return False
119116
else:
120117
if not stat.S_ISDIR(st.st_mode):
@@ -145,15 +142,14 @@ def _fake_init(self, path: str) -> os.stat_result:
145142
assert basename == "__init__.py", path
146143
assert not os.path.exists(path), path # Not cached!
147144
dirname = os.path.normpath(dirname)
148-
st = self.stat(dirname) # May raise OSError
145+
st = os.stat(dirname) # May raise OSError
149146
# Get stat result as a list so we can modify it.
150147
seq: list[float] = list(st)
151148
seq[stat.ST_MODE] = stat.S_IFREG | 0o444
152149
seq[stat.ST_INO] = 1
153150
seq[stat.ST_NLINK] = 1
154151
seq[stat.ST_SIZE] = 0
155152
st = os.stat_result(seq)
156-
self.stat_cache[path] = st
157153
# Make listdir() and read() also pretend this file exists.
158154
self.fake_package_cache.add(dirname)
159155
return st
@@ -181,9 +177,8 @@ def listdir(self, path: str) -> list[str]:
181177
return results
182178

183179
def isfile(self, path: str) -> bool:
184-
try:
185-
st = self.stat(path)
186-
except OSError:
180+
st = self.stat_or_none(path)
181+
if st is None:
187182
return False
188183
return stat.S_ISREG(st.st_mode)
189184

@@ -248,18 +243,14 @@ def exists_case(self, path: str, prefix: str) -> bool:
248243
return res
249244

250245
def isdir(self, path: str) -> bool:
251-
try:
252-
st = self.stat(path)
253-
except OSError:
246+
st = self.stat_or_none(path)
247+
if st is None:
254248
return False
255249
return stat.S_ISDIR(st.st_mode)
256250

257251
def exists(self, path: str) -> bool:
258-
try:
259-
self.stat(path)
260-
except FileNotFoundError:
261-
return False
262-
return True
252+
st = self.stat_or_none(path)
253+
return st is not None
263254

264255
def read(self, path: str) -> bytes:
265256
if path in self.read_cache:
@@ -269,7 +260,7 @@ def read(self, path: str) -> bytes:
269260

270261
# Need to stat first so that the contents of file are from no
271262
# earlier instant than the mtime reported by self.stat().
272-
self.stat(path)
263+
self.stat_or_none(path)
273264

274265
dirname, basename = os.path.split(path)
275266
dirname = os.path.normpath(dirname)
@@ -294,8 +285,10 @@ def hash_digest(self, path: str) -> str:
294285
return self.hash_cache[path]
295286

296287
def samefile(self, f1: str, f2: str) -> bool:
297-
s1 = self.stat(f1)
298-
s2 = self.stat(f2)
288+
s1 = self.stat_or_none(f1)
289+
s2 = self.stat_or_none(f2)
290+
if s1 is None or s2 is None:
291+
return False
299292
return os.path.samestat(s1, s2)
300293

301294

mypy/fswatcher.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from __future__ import annotations
44

5+
import os
56
from typing import AbstractSet, Iterable, NamedTuple
67

78
from mypy.fscache import FileSystemCache
@@ -56,18 +57,16 @@ def remove_watched_paths(self, paths: Iterable[str]) -> None:
5657
del self._file_data[path]
5758
self._paths -= set(paths)
5859

59-
def _update(self, path: str) -> None:
60-
st = self.fs.stat(path)
60+
def _update(self, path: str, st: os.stat_result) -> None:
6161
hash_digest = self.fs.hash_digest(path)
6262
self._file_data[path] = FileData(st.st_mtime, st.st_size, hash_digest)
6363

6464
def _find_changed(self, paths: Iterable[str]) -> AbstractSet[str]:
6565
changed = set()
6666
for path in paths:
6767
old = self._file_data[path]
68-
try:
69-
st = self.fs.stat(path)
70-
except FileNotFoundError:
68+
st = self.fs.stat_or_none(path)
69+
if st is None:
7170
if old is not None:
7271
# File was deleted.
7372
changed.add(path)
@@ -76,13 +75,13 @@ def _find_changed(self, paths: Iterable[str]) -> AbstractSet[str]:
7675
if old is None:
7776
# File is new.
7877
changed.add(path)
79-
self._update(path)
78+
self._update(path, st)
8079
# Round mtimes down, to match the mtimes we write to meta files
8180
elif st.st_size != old.st_size or int(st.st_mtime) != int(old.st_mtime):
8281
# Only look for changes if size or mtime has changed as an
8382
# optimization, since calculating hash is expensive.
8483
new_hash = self.fs.hash_digest(path)
85-
self._update(path)
84+
self._update(path, st)
8685
if st.st_size != old.st_size or new_hash != old.hash:
8786
# Changed file.
8887
changed.add(path)

0 commit comments

Comments
 (0)