Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions mypy/nativeparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
read_str_opt,
read_tag,
)
from mypy.errors import CompileError
from mypy.nodes import (
ARG_KINDS,
ARG_POS,
Expand Down Expand Up @@ -164,6 +165,53 @@
# There is no way to create reasonable fallbacks at this stage,
# they must be patched later.
_dummy_fallback: Final = Instance(MISSING_FALLBACK, [], -1)
_AST_SERIALIZE_REQUIREMENT: Final = "ast-serialize>=0.5.0,<1.0.0"
_AST_SERIALIZE_MIN_VERSION: Final = (0, 5, 0)
_AST_SERIALIZE_MAX_VERSION: Final = (1, 0, 0)


def _parse_ast_serialize_version(version: str) -> tuple[int, int, int] | None:
release = version.split("+", 1)[0].split("-", 1)[0]
components = release.split(".")
if len(components) > 3:
components = components[:3]

parts: list[int] = []
for component in components:
if not component.isdigit():
return None
parts.append(int(component))

while len(parts) < 3:
parts.append(0)
return parts[0], parts[1], parts[2]


def _validate_ast_serialize_version() -> None:
from importlib import metadata

try:
version = metadata.version("ast-serialize")
except metadata.PackageNotFoundError:
return

parsed_version = _parse_ast_serialize_version(version)
if parsed_version is None:
return
if parsed_version < _AST_SERIALIZE_MIN_VERSION:
raise CompileError(
[
f"mypy: error: ast-serialize {version} is too old; "
f"mypy requires {_AST_SERIALIZE_REQUIREMENT}"
]
)
if parsed_version >= _AST_SERIALIZE_MAX_VERSION:
raise CompileError(
[
f"mypy: error: ast-serialize {version} is too new; "
f"mypy requires {_AST_SERIALIZE_REQUIREMENT}"
]
)


class State:
Expand Down Expand Up @@ -250,6 +298,8 @@ def read_statements(state: State, data: ReadBuffer, n: int) -> list[Statement]:
def parse_to_binary_ast(
filename: str, options: Options, skip_function_bodies: bool = False
) -> tuple[bytes, list[ParseError], TypeIgnores, bytes, bool, bool, str, list[tuple[int, str]]]:
_validate_ast_serialize_version()

# This is a horrible hack to work around a mypyc bug where imported
# module may be not ready in a thread sometimes.
t0 = time.time()
Expand Down
45 changes: 45 additions & 0 deletions mypy/test/test_nativeparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import tempfile
import unittest
from collections.abc import Iterator
from importlib import metadata
from unittest.mock import patch

from librt.internal import ReadBuffer

Expand All @@ -36,6 +38,7 @@
# If the experimental ast_serialize module isn't installed, the following import will fail
# and we won't run any native parser tests.
try:
from mypy import nativeparse
from mypy.nativeparse import (
State,
deserialize_imports,
Expand Down Expand Up @@ -232,6 +235,48 @@ def format_reachable_imports(node: MypyFile) -> list[str]:
return output


@unittest.skipUnless(has_nativeparse, "nativeparse not available")
class TestNativeParserVersion(unittest.TestCase):
def test_ast_serialize_version_accepts_minimum(self) -> None:
with patch("importlib.metadata.version", return_value="0.5.0"):
nativeparse._validate_ast_serialize_version()

def test_ast_serialize_version_reports_old_version(self) -> None:
for version in ["0.2.3", "0.4.0"]:
with self.subTest(version=version):
with patch("importlib.metadata.version", return_value=version):
with self.assertRaises(CompileError) as context:
nativeparse._validate_ast_serialize_version()

self.assertEqual(
context.exception.messages,
[
f"mypy: error: ast-serialize {version} is too old; "
"mypy requires ast-serialize>=0.5.0,<1.0.0"
],
)

def test_ast_serialize_version_reports_too_new_version(self) -> None:
with patch("importlib.metadata.version", return_value="1.0.0"):
with self.assertRaises(CompileError) as context:
nativeparse._validate_ast_serialize_version()

self.assertEqual(
context.exception.messages,
[
"mypy: error: ast-serialize 1.0.0 is too new; "
"mypy requires ast-serialize>=0.5.0,<1.0.0"
],
)

def test_ast_serialize_version_ignores_missing_metadata(self) -> None:
with patch(
"importlib.metadata.version",
side_effect=metadata.PackageNotFoundError("ast-serialize"),
):
nativeparse._validate_ast_serialize_version()


@unittest.skipUnless(has_nativeparse, "nativeparse not available")
class TestNativeParserBinaryFormat(unittest.TestCase):
def test_trivial_binary_data(self) -> None:
Expand Down
Loading