diff --git a/mypy/fastparse.py b/mypy/fastparse.py index 1d6dec891dd97..10ad642dcdf0a 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -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 diff --git a/test-data/unit/check-newsyntax.test b/test-data/unit/check-newsyntax.test index 9f3f87853e63b..645fbe525358c 100644 --- a/test-data/unit/check-newsyntax.test +++ b/test-data/unit/check-newsyntax.test @@ -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") @@ -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] + diff --git a/test-data/unit/fixtures/f_string.pyi b/test-data/unit/fixtures/f_string.pyi new file mode 100644 index 0000000000000..78d39aee85b82 --- /dev/null +++ b/test-data/unit/fixtures/f_string.pyi @@ -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 +