Repository: python/mypy

Issue:
Incorrect errors for fstrings + generators
mypy version: 0.511
python version: 3.6.1

Given this valid program:

``` py
from typing import Iterator

def stringify(it: Iterator[int]) -> Iterator[str]:
    return (f"{x}" for x in it)

def stringify2(it: Iterator[int]) -> Iterator[str]:
    for x in it:
        yield f"{x}"

def stringify3(it: Iterator[int]) -> Iterator[str]:
    return (str(x) for x in it)

def stringify4(it: Iterator[int]) -> Iterator[str]:
    for x in it:
        yield str(x)
```

I get errors for the first two variations:

```
foo.py:4: error: Generator has incompatible item type "int"
foo.py:8: error: Incompatible types in yield (actual type "int", expected type "str")
```

It seems that mypy doesn't recognize that fstrings are produce a `str`. Interestingly, the yield error didn't show up in my old mypy version 0.501.


Discussion:
Strangely, this is fine:

```py
def stringify5(it: Iterator[Tuple[int, int]]) -> Iterator[str]:
    return (f"({x}, {y})" for x, y in it)
```

... but this is not:

```py
class Point:
    def __init__(self, x, y) -> None:
        self.x = x
        self.y = y

    def __str__(self) -> str:
        return f"({self.x}, {self.y})"

def stringify6(it: Iterator[Point]) -> Iterator[str]:
    return (f"{pt}" for pt in it)
```

So it seems like it's the unpacking syntax (`... for x, y in ...`) that makes the difference, and not whether or not the iterator type is primitive.
It looks like it doesn't have anything to do with the unpacking; it seems to pertain to the handling of `ast.FormattedValue`. Mypy handles this correctly:

```py
def stringify(it: Iterator[int]) -> Iterator[str]:
    return (f"asdf {x}" for x in it)
```

According to [GreenTreeSnakes' FormattedValue docs][0]:

> If the string contains a single formatting field and nothing else the node can be isolated otherwise it appears in JoinedStr.

In this most recent example, using an fstring with tokens besides the formatted value produces a JoinedStr token. Maybe mypy handles JoinedStr correctly but not FormattedValue?

[0]: http://greentreesnakes.readthedocs.io/en/latest/nodes.html
Yes, it looks like the bug is that mypy interprets `f"{x}"` as having the type of `x`.

Resolving patch:
diff --git a/mypy/fastparse.py b/mypy/fastparse.py
--- 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


Test patch:
diff --git a/test-data/unit/check-newsyntax.test b/test-data/unit/check-newsyntax.test
--- 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]
+