Skip to content

Commit

Permalink
Keep track of fstring conversions and format-specs (python#6965)
Browse files Browse the repository at this point in the history
Modify fastparse.py to keep track of format specifiers and conversions for f-strings. 
Currently, braces values within f-strings are just converted to calls to format,
ignoring format specifiers and conversions ('{}'.format(...)). 
With this the information regarding format specifiers and conversions are passed along to format.
To accomodate format specifiers with expressions evaluated at runtime, we pass the
format specifier as an argument also (ex: '{!r{}}.format(..., "<30")').
Note that format specifiers can contain expressions that are evaluated at runtime.

Keeping track of this information is needed in order to provide mypyc with enough information to support fstrings with format specifiers and conversions.
  • Loading branch information
SanjitKal authored and msullivan committed Jun 12, 2019
1 parent 4bef810 commit e06378c
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 5 deletions.
16 changes: 11 additions & 5 deletions mypy/fastparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -1099,6 +1099,9 @@ def visit_JoinedStr(self, n: ast3.JoinedStr) -> Expression:
empty_string.set_line(n.lineno, n.col_offset)
strs_to_join = ListExpr(self.translate_expr_list(n.values))
strs_to_join.set_line(empty_string)
# Don't make unecessary join call if there is only one str to join
if len(strs_to_join.items) == 1:
return self.set_line(strs_to_join.items[0], n)
join_method = MemberExpr(empty_string, 'join')
join_method.set_line(empty_string)
result_expression = CallExpr(join_method,
Expand All @@ -1111,15 +1114,18 @@ def visit_JoinedStr(self, n: ast3.JoinedStr) -> Expression:
def visit_FormattedValue(self, n: ast3.FormattedValue) -> Expression:
# A FormattedValue is a component of a JoinedStr, or it can exist
# on its own. We translate them to individual '{}'.format(value)
# calls -- we don't bother with the conversion/format_spec fields.
exp = self.visit(n.value)
exp.set_line(n.lineno, n.col_offset)
format_string = StrExpr('{}')
# calls. Format specifier and conversion information is passed along
# to allow mypyc to support f-strings with format specifiers and conversions.
val_exp = self.visit(n.value)
val_exp.set_line(n.lineno, n.col_offset)
conv_str = '' if n.conversion is None or n.conversion < 0 else '!' + chr(n.conversion)
format_string = StrExpr('{' + conv_str + ':{}}')
format_spec_exp = self.visit(n.format_spec) if n.format_spec is not None else StrExpr('')
format_string.set_line(n.lineno, n.col_offset)
format_method = MemberExpr(format_string, 'format')
format_method.set_line(format_string)
result_expression = CallExpr(format_method,
[exp],
[val_exp, format_spec_exp],
[ARG_POS],
[None])
return self.set_line(result_expression, n)
Expand Down
119 changes: 119 additions & 0 deletions test-data/unit/parse.test
Original file line number Diff line number Diff line change
Expand Up @@ -3323,3 +3323,122 @@ MypyFile:1(
Block:1(
ExpressionStmt:2(
YieldExpr:2()))))

[case testFStringSimple]
x = 'mypy'
f'Hello {x}'
[out]
MypyFile:1(
AssignmentStmt:1(
NameExpr(x)
StrExpr(mypy))
ExpressionStmt:2(
CallExpr:2(
MemberExpr:2(
StrExpr()
join)
Args(
ListExpr:2(
StrExpr(Hello )
CallExpr:2(
MemberExpr:2(
StrExpr({:{}})
format)
Args(
NameExpr(x))))))))

[case testFStringWithConversion]
x = 'mypy'
F'Hello {x!r}'
[out]
MypyFile:1(
AssignmentStmt:1(
NameExpr(x)
StrExpr(mypy))
ExpressionStmt:2(
CallExpr:2(
MemberExpr:2(
StrExpr()
join)
Args(
ListExpr:2(
StrExpr(Hello )
CallExpr:2(
MemberExpr:2(
StrExpr({!r:{}})
format)
Args(
NameExpr(x))))))))

[case testFStringWithOnlyFormatSpecifier]
x = 'mypy'
f'Hello {x:<30}'
[out]
MypyFile:1(
AssignmentStmt:1(
NameExpr(x)
StrExpr(mypy))
ExpressionStmt:2(
CallExpr:2(
MemberExpr:2(
StrExpr()
join)
Args(
ListExpr:2(
StrExpr(Hello )
CallExpr:2(
MemberExpr:2(
StrExpr({:{}})
format)
Args(
NameExpr(x))))))))

[case testFStringWithFormatSpecifierAndConversion]
x = 'mypy'
f'Hello {x!s:<30}'
[out]
MypyFile:1(
AssignmentStmt:1(
NameExpr(x)
StrExpr(mypy))
ExpressionStmt:2(
CallExpr:2(
MemberExpr:2(
StrExpr()
join)
Args(
ListExpr:2(
StrExpr(Hello )
CallExpr:2(
MemberExpr:2(
StrExpr({!s:{}})
format)
Args(
NameExpr(x))))))))

[case testFStringWithFormatSpecifierExpression]
x = 'mypy'
y = 30
f'Hello {x!s:<{y+y}}'
[out]
MypyFile:1(
AssignmentStmt:1(
NameExpr(x)
StrExpr(mypy))
AssignmentStmt:2(
NameExpr(y)
IntExpr(30))
ExpressionStmt:3(
CallExpr:3(
MemberExpr:3(
StrExpr()
join)
Args(
ListExpr:3(
StrExpr(Hello )
CallExpr:3(
MemberExpr:3(
StrExpr({!s:{}})
format)
Args(
NameExpr(x))))))))

0 comments on commit e06378c

Please sign in to comment.