Repository: python/mypy

Issue:
Invalid `error: On Python 3 '{}'.format(b'abc') produces "b'abc'", not 'abc'; use '{!r}'.format(b'abc') if this is desired behavior` when using formatted string literals
**Bug Report**

This particular check seems to be incompatible with formatted string literals.

**To Reproduce**

```
$ mypy -c 'x = b""; f"{x}"'
<string>:1: error: On Python 3 '{}'.format(b'abc') produces "b'abc'", not 'abc'; use '{!r}'.format(b'abc') if this is desired behavior
Found 1 error in 1 file (checked 1 source file)

$ mypy -c 'x = b""; f"{x:r}"'
<string>:1: error: On Python 3 '{}'.format(b'abc') produces "b'abc'", not 'abc'; use '{!r}'.format(b'abc') if this is desired behavior
Found 1 error in 1 file (checked 1 source file)

$ mypy --version
mypy 0.910
```

**Expected Behavior**

Should work in the same way as with `str.format()`:

```
$ mypy -c 'x = b""; "{x}".format(x=x)'
<string>:1: error: On Python 3 '{}'.format(b'abc') produces "b'abc'", not 'abc'; use '{!r}'.format(b'abc') if this is desired behavior
Found 1 error in 1 file (checked 1 source file)

$ mypy -c 'x = b""; "{x:r}".format(x=x)'
Success: no issues found in 1 source file
```

**Actual Behavior**

See **To Reproduce**

**Your Environment**

- Mypy version used: 0.910
- Mypy command-line flags: (see above)
- Mypy configuration options from `mypy.ini` (and other config files): none
- Python version used: 3.9.6
- Operating system and version: Fedora 34 x86_64


Discussion:
Oh, it should be `f'{x!r}'`. Sorry, my fault.
For f-strings, the error message shouldn't talk about `.format()` though. Re-opening to track improving that error message.
Currently f-strings is represented as several str.format() calls on mypy AST.

Resolving patch:
diff --git a/mypy/checkstrformat.py b/mypy/checkstrformat.py
--- a/mypy/checkstrformat.py
+++ b/mypy/checkstrformat.py
@@ -380,8 +380,9 @@ def perform_special_format_checks(self, spec: ConversionSpecifier, call: CallExp
                 if (has_type_component(actual_type, 'builtins.bytes') and
                         not custom_special_method(actual_type, '__str__')):
                     self.msg.fail(
-                        "On Python 3 '{}'.format(b'abc') produces \"b'abc'\", not 'abc'; "
-                        "use '{!r}'.format(b'abc') if this is desired behavior",
+                        'On Python 3 formatting "b\'abc\'" with "{}" '
+                        'produces "b\'abc\'", not "abc"; '
+                        'use "{!r}" if this is desired behavior',
                         call, code=codes.STR_BYTES_PY3)
         if spec.flags:
             numeric_types = UnionType([self.named_type('builtins.int'),
@@ -836,8 +837,9 @@ def check_s_special_cases(self, expr: FormatStringExpr, typ: Type, context: Cont
             if self.chk.options.python_version >= (3, 0):
                 if has_type_component(typ, 'builtins.bytes'):
                     self.msg.fail(
-                        "On Python 3 '%s' % b'abc' produces \"b'abc'\", not 'abc'; "
-                        "use '%r' % b'abc' if this is desired behavior",
+                        'On Python 3 formatting "b\'abc\'" with "%s" '
+                        'produces "b\'abc\'", not "abc"; '
+                        'use "%r" if this is desired behavior',
                         context, code=codes.STR_BYTES_PY3)
                     return False
             if self.chk.options.python_version < (3, 0):


Test patch:
diff --git a/mypyc/test-data/run-strings.test b/mypyc/test-data/run-strings.test
--- a/mypyc/test-data/run-strings.test
+++ b/mypyc/test-data/run-strings.test
@@ -218,8 +218,8 @@ def test_fstring_basics() -> None:
 
     x = bytes([1, 2, 3, 4])
     # assert f'bytes: {x}' == "bytes: b'\\x01\\x02\\x03\\x04'"
-    # error: On Python 3 '{}'.format(b'abc') produces "b'abc'", not 'abc';
-    #        use '{!r}'.format(b'abc') if this is desired behavior
+    # error: On Python 3 formatting "b'abc'" with "{}" produces "b'abc'", not "abc";
+    #        use "{!r}" if this is desired behavior behavior
 
     float_num = 123.4
     assert f'{float_num}' == '123.4'
@@ -354,7 +354,7 @@ def test_format_method_basics() -> None:
 def test_format_method_empty_braces() -> None:
     name = 'Eric'
     age = 14
-    
+
     assert 'Hello, {}!'.format(name) == 'Hello, Eric!'
     assert '{}'.format(name) == 'Eric'
     assert '{}! Hi!'.format(name) == 'Eric! Hi!'
diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test
--- a/test-data/unit/check-errorcodes.test
+++ b/test-data/unit/check-errorcodes.test
@@ -641,8 +641,8 @@ def g() -> int:
 '%d' % 'no'  # E: Incompatible types in string interpolation (expression has type "str", placeholder has type "Union[int, float, SupportsInt]")  [str-format]
 '%d + %d' % (1, 2, 3)  # E: Not all arguments converted during string formatting  [str-format]
 
-'{}'.format(b'abc')  # E: On Python 3 '{}'.format(b'abc') produces "b'abc'", not 'abc'; use '{!r}'.format(b'abc') if this is desired behavior  [str-bytes-safe]
-'%s' % b'abc'  # E: On Python 3 '%s' % b'abc' produces "b'abc'", not 'abc'; use '%r' % b'abc' if this is desired behavior  [str-bytes-safe]
+'{}'.format(b'abc')  # E: On Python 3 formatting "b'abc'" with "{}" produces "b'abc'", not "abc"; use "{!r}" if this is desired behavior  [str-bytes-safe]
+'%s' % b'abc'  # E: On Python 3 formatting "b'abc'" with "%s" produces "b'abc'", not "abc"; use "%r" if this is desired behavior  [str-bytes-safe]
 [builtins fixtures/primitives.pyi]
 [typing fixtures/typing-medium.pyi]
 
diff --git a/test-data/unit/check-expressions.test b/test-data/unit/check-expressions.test
--- a/test-data/unit/check-expressions.test
+++ b/test-data/unit/check-expressions.test
@@ -1183,8 +1183,8 @@ xb: bytes
 xs: str
 
 '%s' % xs   # OK
-'%s' % xb   # E: On Python 3 '%s' % b'abc' produces "b'abc'", not 'abc'; use '%r' % b'abc' if this is desired behavior
-'%(name)s' % {'name': b'value'}  # E: On Python 3 '%s' % b'abc' produces "b'abc'", not 'abc'; use '%r' % b'abc' if this is desired behavior
+'%s' % xb   # E: On Python 3 formatting "b'abc'" with "%s" produces "b'abc'", not "abc"; use "%r" if this is desired behavior
+'%(name)s' % {'name': b'value'}  # E: On Python 3 formatting "b'abc'" with "%s" produces "b'abc'", not "abc"; use "%r" if this is desired behavior
 [builtins fixtures/primitives.pyi]
 
 [case testStringInterpolationSBytesVsStrResultsPy2]
@@ -1617,22 +1617,32 @@ N = NewType('N', bytes)
 n: N
 
 '{}'.format(a)
-'{}'.format(b)  # E: On Python 3 '{}'.format(b'abc') produces "b'abc'", not 'abc'; use '{!r}'.format(b'abc') if this is desired behavior
-'{}'.format(x)  # E: On Python 3 '{}'.format(b'abc') produces "b'abc'", not 'abc'; use '{!r}'.format(b'abc') if this is desired behavior
-'{}'.format(n)  # E: On Python 3 '{}'.format(b'abc') produces "b'abc'", not 'abc'; use '{!r}'.format(b'abc') if this is desired behavior
+'{}'.format(b)  # E: On Python 3 formatting "b'abc'" with "{}" produces "b'abc'", not "abc"; use "{!r}" if this is desired behavior
+'{}'.format(x)  # E: On Python 3 formatting "b'abc'" with "{}" produces "b'abc'", not "abc"; use "{!r}" if this is desired behavior
+'{}'.format(n)  # E: On Python 3 formatting "b'abc'" with "{}" produces "b'abc'", not "abc"; use "{!r}" if this is desired behavior
+
+f'{b}'  # E: On Python 3 formatting "b'abc'" with "{}" produces "b'abc'", not "abc"; use "{!r}" if this is desired behavior
+f'{x}'  # E: On Python 3 formatting "b'abc'" with "{}" produces "b'abc'", not "abc"; use "{!r}" if this is desired behavior
+f'{n}'  # E: On Python 3 formatting "b'abc'" with "{}" produces "b'abc'", not "abc"; use "{!r}" if this is desired behavior
 
 class C(Generic[B]):
     x: B
     def meth(self) -> None:
-        '{}'.format(self.x)  # E: On Python 3 '{}'.format(b'abc') produces "b'abc'", not 'abc'; use '{!r}'.format(b'abc') if this is desired behavior
+        '{}'.format(self.x)  # E: On Python 3 formatting "b'abc'" with "{}" produces "b'abc'", not "abc"; use "{!r}" if this is desired behavior
 
 def func(x: A) -> A:
-    '{}'.format(x)  # E: On Python 3 '{}'.format(b'abc') produces "b'abc'", not 'abc'; use '{!r}'.format(b'abc') if this is desired behavior
+    '{}'.format(x)  # E: On Python 3 formatting "b'abc'" with "{}" produces "b'abc'", not "abc"; use "{!r}" if this is desired behavior
     return x
 
+'{!r}'.format(a)
 '{!r}'.format(b)
 '{!r}'.format(x)
 '{!r}'.format(n)
+f'{a}'
+f'{a!r}'
+f'{b!r}'
+f'{x!r}'
+f'{n!r}'
 
 class D(bytes):
     def __str__(self) -> str: