Skip to content

Commit 1e516b2

Browse files
authored
Add t-strings support to native parser (#21007)
This is mypy counterpart of mypyc/ast_serialize#34
1 parent 537740b commit 1e516b2

File tree

5 files changed

+48
-18
lines changed

5 files changed

+48
-18
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ jobs:
220220
# To speed-up process until ast_serialize is on PyPI.
221221
- name: Install pinned ast-serialize
222222
if: ${{ matrix.dev_ast_serialize }}
223-
run: pip install ast-serialize@git+https://github.com/mypyc/ast_serialize.git@da5a16cf268dbec63ed6b2e6b715470576e2d1a6
223+
run: pip install ast-serialize@git+https://github.com/mypyc/ast_serialize.git@d277690a078c7784667a640ed1045e725bc42c00
224224

225225
- name: Setup tox environment
226226
run: |

mypy/nativeparse.py

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@
111111
Statement,
112112
StrExpr,
113113
SuperExpr,
114+
TemplateStrExpr,
114115
TempNode,
115116
TryStmt,
116117
TupleExpr,
@@ -168,6 +169,7 @@ def __init__(self, options: Options) -> None:
168169
self.options = options
169170
self.errors: list[dict[str, Any]] = []
170171
self.num_funcs = 0
172+
self.uses_template_strings = False
171173

172174
def add_error(
173175
self,
@@ -233,6 +235,7 @@ def native_parse(
233235
node = MypyFile(defs, imports)
234236
node.path = filename
235237
node.is_partial_stub_package = is_partial_package
238+
node.uses_template_strings = state.uses_template_strings
236239
# Merge deserialization errors with parsing errors
237240
all_errors = errors + state.errors
238241
return node, all_errors, ignores
@@ -243,7 +246,7 @@ def expect_end_tag(data: ReadBuffer) -> None:
243246

244247

245248
def expect_tag(data: ReadBuffer, tag: Tag) -> None:
246-
assert read_tag(data) == tag
249+
assert (actual := read_tag(data)) == tag, actual
247250

248251

249252
def read_statements(state: State, data: ReadBuffer, n: int) -> list[Statement]:
@@ -261,9 +264,9 @@ def read_statements(state: State, data: ReadBuffer, n: int) -> list[Statement]:
261264
def parse_to_binary_ast(
262265
filename: str, options: Options, skip_function_bodies: bool = False
263266
) -> tuple[bytes, list[dict[str, Any]], TypeIgnores, bytes, bool]:
264-
ast_bytes, errors, ignores, import_bytes, is_partial_package = ast_serialize.parse(
267+
ast_bytes, errors, ignores, import_bytes, ast_data = ast_serialize.parse(
265268
filename,
266-
skip_function_bodies,
269+
skip_function_bodies=skip_function_bodies,
267270
python_version=options.python_version,
268271
platform=options.platform,
269272
always_true=options.always_true,
@@ -274,7 +277,7 @@ def parse_to_binary_ast(
274277
cast("list[dict[str, Any]]", errors),
275278
ignores,
276279
import_bytes,
277-
is_partial_package,
280+
ast_data["is_partial_package"],
278281
)
279282

280283

@@ -1524,6 +1527,32 @@ def read_expression(state: State, data: ReadBuffer) -> Expression:
15241527
expr = build_fstring_join(state, data, fitems)
15251528
expect_end_tag(data)
15261529
return expr
1530+
elif tag == nodes.TSTRING_EXPR:
1531+
state.uses_template_strings = True
1532+
nparts = read_int(data)
1533+
titems: list[Expression | tuple[Expression, str, str | None, Expression | None]] = []
1534+
for _ in range(nparts):
1535+
if read_bool(data):
1536+
e = read_expression(state, data)
1537+
s = read_str(data)
1538+
if read_bool(data):
1539+
conv = read_str(data)
1540+
else:
1541+
conv = None
1542+
if read_bool(data):
1543+
# Parse format spec as a JoinedStr, this matches the old parser behavior.
1544+
format_spec = read_fstring_items(state, data)
1545+
else:
1546+
format_spec = None
1547+
titems.append((e, s, conv, format_spec))
1548+
else:
1549+
s = StrExpr(read_str(data))
1550+
read_loc(data, s)
1551+
titems.append(s)
1552+
expr = TemplateStrExpr(titems)
1553+
read_loc(data, expr)
1554+
expect_end_tag(data)
1555+
return expr
15271556
elif tag == nodes.LAMBDA_EXPR:
15281557
arguments, has_ann = read_parameters(state, data)
15291558
body = read_block(state, data)

mypy/nodes.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5242,6 +5242,7 @@ def local_definitions(
52425242
IMPORT_METADATA: Final[Tag] = 226
52435243
IMPORTFROM_METADATA: Final[Tag] = 227
52445244
IMPORTALL_METADATA: Final[Tag] = 228
5245+
TSTRING_EXPR: Final[Tag] = 229
52455246

52465247

52475248
def read_symbol(data: ReadBuffer) -> SymbolNode:

test-data/unit/check-python314.test

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
[case testTemplateStringBasics_no_native_parse]
1+
[case testTemplateStringBasics]
22
reveal_type(t"foobar") # N: Revealed type is "string.templatelib.Template"
33
t"{'foobar'}"
44
t"foo{'bar'}"
@@ -14,32 +14,32 @@ a = t"foobar"
1414
a = t"{'foobar'}"
1515
[builtins fixtures/f_string.pyi]
1616

17-
[case testTemplateStringWithoutExplicitImport_no_native_parse]
17+
[case testTemplateStringWithoutExplicitImport]
1818
reveal_type(t"implicit import works") # N: Revealed type is "string.templatelib.Template"
1919
[builtins fixtures/f_string.pyi]
2020

21-
[case testTemplateStringExpressionsOk_no_native_parse]
21+
[case testTemplateStringExpressionsOk]
2222
t".{1 + 1}."
2323
t".{1 + 1}.{'foo' + 'bar'}"
2424
[builtins fixtures/f_string.pyi]
2525

26-
[case testTemplateStringExpressionsErrors_no_native_parse]
26+
[case testTemplateStringExpressionsErrors]
2727
t"{1 + ''}" # E: Unsupported operand types for + ("int" and "str")
2828
t".{1 + ''}" # E: Unsupported operand types for + ("int" and "str")
2929
[builtins fixtures/f_string.pyi]
3030

31-
[case testTemplateStringParseFormatOptions_no_native_parse]
31+
[case testTemplateStringParseFormatOptions]
3232
value = 10.5142
3333
width = 10
3434
precision = 4
3535
t"result: {value:{width}.{precision}}"
3636
[builtins fixtures/f_string.pyi]
3737

38-
[case testTemplateStringNestedExpressionsTypeChecked_no_native_parse]
38+
[case testTemplateStringNestedExpressionsTypeChecked]
3939
t"{2:{3 + ''}}" # E: Unsupported operand types for + ("int" and "str")
4040
[builtins fixtures/f_string.pyi]
4141

42-
[case testIncrementalTemplateStringImplicitDependency_no_native_parse]
42+
[case testIncrementalTemplateStringImplicitDependency]
4343
import m
4444
reveal_type(m.x)
4545
[file m.py]

test-data/unit/native-parser.test

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2552,7 +2552,7 @@ MypyFile:1(
25522552
x = [1 2]
25532553
y = 2
25542554
[out]
2555-
1:8: error: Expected ',', found int
2555+
1:8: error: Expected `,`, found int
25562556
MypyFile:1(
25572557
AssignmentStmt:1(
25582558
NameExpr(x)
@@ -2603,7 +2603,7 @@ MypyFile:1(
26032603
from m
26042604
1
26052605
[out]
2606-
1:7: error: Expected 'import', found newline
2606+
1:7: error: Expected `import`, found newline
26072607
MypyFile:1(
26082608
ImportFrom:1(m, [])
26092609
ExpressionStmt:2(
@@ -2613,7 +2613,7 @@ MypyFile:1(
26132613
from m
26142614
1
26152615
[out]
2616-
1:7: error: Expected 'import', found newline
2616+
1:7: error: Expected `import`, found newline
26172617
MypyFile:1(
26182618
ImportFrom:1(m, [])
26192619
ExpressionStmt:2(
@@ -2626,7 +2626,7 @@ def f():
26262626

26272627
def g(): ...
26282628
[out]
2629-
3:8: error: Expected ')', found newline
2629+
3:8: error: Expected `)`, found newline
26302630
MypyFile:1(
26312631
FuncDef:1(
26322632
f
@@ -2651,7 +2651,7 @@ def f(
26512651

26522652
def g(): ...
26532653
[out]
2654-
3:6: error: Expected ')', found newline
2654+
3:6: error: Expected `)`, found newline
26552655
5:1: error: Expected an indented block after function definition
26562656
MypyFile:1(
26572657
FuncDef:1(
@@ -2671,7 +2671,7 @@ class A
26712671

26722672
def f(): pass
26732673
[out]
2674-
1:8: error: Expected ':', found newline
2674+
1:8: error: Expected `:`, found newline
26752675
3:1: error: Expected an indented block after `class` definition
26762676
MypyFile:1(
26772677
ClassDef:1(

0 commit comments

Comments
 (0)