Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parse each format-string component separately #3390

Merged
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
35 changes: 24 additions & 11 deletions mypy/fastparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -858,22 +858,35 @@ def visit_Str(self, n: ast3.Str) -> Union[UnicodeExpr, StrExpr]:
# JoinedStr(expr* values)
@with_line
def visit_JoinedStr(self, n: ast3.JoinedStr) -> Expression:
arg_count = len(n.values)
format_string = StrExpr('{}' * arg_count)
format_string.set_line(n.lineno, n.col_offset)
format_method = MemberExpr(format_string, 'format')
format_method.set_line(format_string)
format_args = self.translate_expr_list(n.values)
format_arg_kinds = [ARG_POS] * arg_count
result_expression = CallExpr(format_method,
format_args,
format_arg_kinds)
# Each of n.values is a str or FormattedValue; we just concatenate
# them all using ''.join.
empty_string = StrExpr('')
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)
join_method = MemberExpr(empty_string, 'join')
join_method.set_line(empty_string)
result_expression = CallExpr(join_method,
[strs_to_join],
[ARG_POS])
return result_expression

# FormattedValue(expr value)
@with_line
def visit_FormattedValue(self, n: ast3.FormattedValue) -> Expression:
return self.visit(n.value)
# 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('{}')
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],
[ARG_POS])
return result_expression

# Bytes(bytes s)
@with_line
Expand Down
16 changes: 12 additions & 4 deletions test-data/unit/check-newsyntax.test
Original file line number Diff line number Diff line change
Expand Up @@ -119,19 +119,19 @@ f'{type(1)}'
a: str
a = f'foobar'
a = f'{"foobar"}'
[builtins fixtures/primitives.pyi]
[builtins fixtures/f_string.pyi]

[case testNewSyntaxFStringExpressionsOk]
# flags: --python-version 3.6
f'.{1 + 1}.'
f'.{1 + 1}.{"foo" + "bar"}'
[builtins fixtures/primitives.pyi]
[builtins fixtures/f_string.pyi]

[case testNewSyntaxFStringExpressionsErrors]
# flags: --python-version 3.6
f'{1 + ""}'
f'.{1 + ""}'
[builtins fixtures/primitives.pyi]
[builtins fixtures/f_string.pyi]
[out]
main:2: error: Unsupported operand types for + ("int" and "str")
main:3: error: Unsupported operand types for + ("int" and "str")
Expand All @@ -142,4 +142,12 @@ value = 10.5142
width = 10
precision = 4
f'result: {value:{width}.{precision}}'
[builtins fixtures/primitives.pyi]
[builtins fixtures/f_string.pyi]

[case testNewSyntaxFStringSingleField]
# flags: --python-version 3.6
v = 1
reveal_type(f'{v}') # E: Revealed type is 'builtins.str'
reveal_type(f'{1}') # E: Revealed type is 'builtins.str'
[builtins fixtures/f_string.pyi]

36 changes: 36 additions & 0 deletions test-data/unit/fixtures/f_string.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Builtins stub used for format-string-related test cases.
# We need str and list, and str needs join and format methods.

from typing import TypeVar, Generic, Iterable, Iterator, List, overload

T = TypeVar('T')

class object:
def __init__(self): pass

class type:
def __init__(self, x) -> None: pass

class ellipsis: pass

class list(Iterable[T], Generic[T]):
@overload
def __init__(self) -> None: pass
@overload
def __init__(self, x: Iterable[T]) -> None: pass
def append(self, x: T) -> None: pass

class tuple(Generic[T]): pass

class function: pass
class int:
def __add__(self, i: int) -> int: pass

class float: pass
class bool(int): pass

class str:
def __add__(self, s: str) -> str: pass
def format(self, *args) -> str: pass
def join(self, l: List[str]) -> str: pass