diff --git a/CHANGELOG.md b/CHANGELOG.md index 8883ce517da7..e8633e23c95b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ - No longer provide mypyc-accelerated wheels for macOS x86_64 [mypyc-wheels #119](https://github.com/mypyc/mypy_mypyc-wheels/pull/119) +### Bug Fixes + +- The native parser no longer crashes with a traceback when checking a file + that is not valid UTF-8 (for example, a file with a PEP 263 + ``# coding: latin-1`` declaration); a clean error message is reported + instead. (PR by @inchang-ing) + ## Mypy 2.3 We've just uploaded mypy 2.3.0 to the Python Package Index ([PyPI](https://pypi.org/project/mypy/)). diff --git a/mypy/build.py b/mypy/build.py index a55fe4eb92d5..089ecd6b5c82 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -3231,9 +3231,20 @@ def get_source(self) -> str: def parse_file_inner(self, source: str | None, raw_data: FileRawData | None = None) -> None: t0 = time_ref() - self.tree = self.manager.parse_file( - self.id, self.xpath, source, options=self.options, raw_data=raw_data - ) + try: + self.tree = self.manager.parse_file( + self.id, self.xpath, source, options=self.options, raw_data=raw_data + ) + except ValueError as decodeerr: + # The native parser reads files as UTF-8 and does not support PEP 263 + # 'coding' declarations; surface a user-facing error instead of + # crashing (see issue #22055). + err = ( + f"{self.path}: error: Cannot decode file: {decodeerr}. Note: the native " + "parser requires files to be valid UTF-8 and does not support PEP 263 " + "'coding' declarations." + ) + raise CompileError([err], module_with_blocker=self.id) from decodeerr self.time_spent_us += time_spent_us(t0) def parse_file(self, *, temporary: bool = False, raw_data: FileRawData | None = None) -> None: diff --git a/mypy/test/testapi.py b/mypy/test/testapi.py index 95bd95ece785..3e0e14ab718b 100644 --- a/mypy/test/testapi.py +++ b/mypy/test/testapi.py @@ -1,6 +1,8 @@ from __future__ import annotations +import os import sys +import tempfile from io import StringIO import mypy.api @@ -43,3 +45,21 @@ def test_capture_version(self) -> None: stdout, _, _ = mypy.api.run(["--version"]) assert isinstance(stdout, str) assert stdout != "" + + def test_decode_error_is_reported_not_raised(self) -> None: + """A file that is not valid UTF-8 produces an error, not a crash. + + The native parser reads files as UTF-8 and does not support PEP 263 + 'coding' declarations (issue #22055). + """ + with tempfile.TemporaryDirectory() as tmp_dir: + file_path = os.path.join(tmp_dir, "latin1.py") + # "s = 'café'" encoded as latin-1: invalid UTF-8 bytes. + with open(file_path, "wb") as f: + f.write(b"s = 'caf\xe9'\n") + + stdout, stderr, status = mypy.api.run(["--cache-dir", os.devnull, file_path]) + assert "Cannot decode file" in stderr + assert "did not contain valid UTF-8" in stderr + assert "errors prevented further checking" in stdout + assert status == 2