Skip to content
Merged
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
2 changes: 1 addition & 1 deletion mypy-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
31 changes: 21 additions & 10 deletions mypy/nativeparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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")
Expand All @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]

Expand Down
5 changes: 5 additions & 0 deletions test-data/unit/check-formatting.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]
1 change: 1 addition & 0 deletions test-data/unit/fixtures/primitives.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 20 additions & 0 deletions test-data/unit/native-parser.test
Original file line number Diff line number Diff line change
Expand Up @@ -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())))))))
20 changes: 20 additions & 0 deletions test-data/unit/parse.test
Original file line number Diff line number Diff line change
Expand Up @@ -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())))))))
2 changes: 1 addition & 1 deletion test-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading