From 0be456d0776c0723dd2cee68c9be055475d6096b Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 30 Apr 2026 12:00:59 +0100 Subject: [PATCH] Bump ast-serialize version to 0.2.3 --- mypy-requirements.txt | 2 +- mypy/nativeparse.py | 31 +++++++++++++++++--------- pyproject.toml | 2 +- test-data/unit/check-formatting.test | 5 +++++ test-data/unit/fixtures/primitives.pyi | 1 + test-data/unit/native-parser.test | 20 +++++++++++++++++ test-data/unit/parse.test | 20 +++++++++++++++++ test-requirements.txt | 2 +- 8 files changed, 70 insertions(+), 13 deletions(-) diff --git a/mypy-requirements.txt b/mypy-requirements.txt index ac3b9ca0fa78d..716b6b13052f2 100644 --- a/mypy-requirements.txt +++ b/mypy-requirements.txt @@ -6,4 +6,4 @@ mypy_extensions>=1.0.0 pathspec>=1.0.0 tomli>=1.1.0; python_version<'3.11' librt>=0.9.0; platform_python_implementation != 'PyPy' -ast-serialize>=0.2.2,<1.0.0 +ast-serialize>=0.2.3,<1.0.0 diff --git a/mypy/nativeparse.py b/mypy/nativeparse.py index f8b703c5ac278..b7baede731923 100644 --- a/mypy/nativeparse.py +++ b/mypy/nativeparse.py @@ -1376,7 +1376,7 @@ def read_expression(state: State, data: ReadBuffer) -> Expression: s = StrExpr(read_str(data)) read_loc(data, s) fitems.append(s) - expr = build_fstring_join(state, data, fitems) + expr = build_fstring_join(data, fitems) expect_end_tag(data) return expr elif tag == nodes.LIST_COMPREHENSION: @@ -1577,22 +1577,17 @@ def read_expression(state: State, data: ReadBuffer) -> Expression: def read_fstring_items(state: State, data: ReadBuffer) -> Expression: - items = [] n = read_int(data) - items = [read_fstring_item(state, data) for i in range(n)] - return build_fstring_join(state, data, items) + items = [read_fstring_item(state, data) for _ in range(n)] + return build_fstring_join(data, items) -def build_fstring_join(state: State, data: ReadBuffer, items: list[Expression]) -> Expression: +def build_fstring_join(data: ReadBuffer, items: list[Expression]) -> Expression: + items = collapse_consecutive_str_items(items) if len(items) == 1: expr = items[0] read_loc(data, expr) return expr - if all(isinstance(item, StrExpr) for item in items): - s = "".join([cast(StrExpr, item).value for item in items]) - expr = StrExpr(s) - read_loc(data, expr) - return expr args = ListExpr(items) str_expr = StrExpr("") member = MemberExpr(str_expr, "join") @@ -1604,6 +1599,22 @@ def build_fstring_join(state: State, data: ReadBuffer, items: list[Expression]) return call +def collapse_consecutive_str_items(items: list[Expression]) -> list[Expression]: + if len(items) <= 1: + return items + last = items[0] + new_items = [last] + for item in items[1:]: + if isinstance(last, StrExpr) and isinstance(item, StrExpr): + last.value += item.value + last.end_line = item.end_line + last.end_column = item.end_column + else: + new_items.append(item) + last = item + return new_items + + def read_fstring_item(state: State, data: ReadBuffer) -> Expression: t = read_tag(data) if t == LITERAL_STR: diff --git a/pyproject.toml b/pyproject.toml index e6d83abdf6bbc..4448903659350 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ dependencies = [ "pathspec>=1.0.0", "tomli>=1.1.0; python_version<'3.11'", "librt>=0.9.0; platform_python_implementation != 'PyPy'", - "ast-serialize>=0.2.2,<1.0.0", + "ast-serialize>=0.2.3,<1.0.0", ] dynamic = ["version"] diff --git a/test-data/unit/check-formatting.test b/test-data/unit/check-formatting.test index 5bd42d495abd3..2044454400162 100644 --- a/test-data/unit/check-formatting.test +++ b/test-data/unit/check-formatting.test @@ -632,3 +632,8 @@ Responses.TEMPLATED_WITH_KW.format() # E: Cannot find replacement for named for Responses.NORMAL.format(42) # E: Not all arguments converted during string formatting Responses.NORMAL.format(value=42) # E: Not all arguments converted during string formatting [builtins fixtures/primitives.pyi] + +[case testDebugFStringReprAutoApplied] +test = b"abc" +debug = f"{test=}" # OK, no str/bytes warning since !r is implicit +[builtins fixtures/primitives.pyi] diff --git a/test-data/unit/fixtures/primitives.pyi b/test-data/unit/fixtures/primitives.pyi index 88d698ecb962c..2069f56dd5fc1 100644 --- a/test-data/unit/fixtures/primitives.pyi +++ b/test-data/unit/fixtures/primitives.pyi @@ -35,6 +35,7 @@ class str(Sequence[str]): def __contains__(self, other: object) -> bool: pass def __getitem__(self, item: int) -> str: pass def format(self, *args: object, **kwargs: object) -> str: pass + def join(self, iterable: Iterable[str], /) -> str: pass def split(self, sep: str = ...) -> list[str]: pass class bytes(Sequence[int]): def __iter__(self) -> Iterator[int]: pass diff --git a/test-data/unit/native-parser.test b/test-data/unit/native-parser.test index 0cf2adf6e3a95..8962875a9cf27 100644 --- a/test-data/unit/native-parser.test +++ b/test-data/unit/native-parser.test @@ -3287,3 +3287,23 @@ MypyFile:1( NameExpr(d) ListExpr:1() list?[int?])) + +[case testDebugFString] +f"test {x=}" +[out] +MypyFile:1( + ExpressionStmt:1( + CallExpr:1( + MemberExpr:1( + StrExpr() + join) + Args( + ListExpr:1( + StrExpr(test x=) + CallExpr:1( + MemberExpr:1( + StrExpr({!r:{}}) + format) + Args( + NameExpr(x) + StrExpr()))))))) diff --git a/test-data/unit/parse.test b/test-data/unit/parse.test index c8ebd9dced6ed..1e43600e56898 100644 --- a/test-data/unit/parse.test +++ b/test-data/unit/parse.test @@ -4025,3 +4025,23 @@ MypyFile:1( Args( Var(self)) Block:7())))) + +[case testDebugFString] +f"test {x=}" +[out] +MypyFile:1( + ExpressionStmt:1( + CallExpr:1( + MemberExpr:1( + StrExpr() + join) + Args( + ListExpr:1( + StrExpr(test x=) + CallExpr:1( + MemberExpr:1( + StrExpr({!r:{}}) + format) + Args( + NameExpr(x) + StrExpr()))))))) diff --git a/test-requirements.txt b/test-requirements.txt index c48c7f4cb9ebf..bfa8d3b783e9e 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -4,7 +4,7 @@ # # pip-compile --allow-unsafe --output-file=test-requirements.txt --strip-extras test-requirements.in # -ast-serialize==0.2.2 +ast-serialize==0.2.3 # via -r test-requirements.in attrs==25.4.0 # via -r test-requirements.in