Repository: python/mypy

Issue:
Please include a "how to fix it" example in the error message
Note: if you are reporting a wrong signature of a function or a class in
the standard library, then the typeshed tracker is better suited
for this report: https://github.com/python/typeshed/issues

Please provide more information to help us understand the issue:

* Are you reporting a bug, or opening a feature request?

Feature request. A slightly more clear error message, which might be appreciated by folks newer to Python who aren't as familiar with the mini formatting language. If wanted, I'll contribute a pull request to fix this.

* Please insert below the code you are checking with mypy,
  or a mock-up repro if the source is private. We would appreciate
  if you try to simplify your case to a minimal repro.

```
$ cat tmp.py
print("{}".format(b'abc'))
```

* What is the actual behavior/output?

```
$ mypy tmp.py
tmp.py:1: error: On Python 3 '{}'.format(b'abc') produces "b'abc'"; use !r if this is a desired behavior
Found 1 error in 1 file (checked 1 source file)
```

* What is the behavior/output you expect?

I'd love an example of what exactly to change. Something like:

```
$ mypy tmp.py
tmp.py:1: error: On Python 3 '{}'.format(b'abc') produces "b'abc'"; use !r if this is a desired behavior. Example: '{!r}'.format(b'abc'
Found 1 error in 1 file (checked 1 source file)
```

* What are the versions of mypy and Python you are using?

```
$ python3 --version
Python 3.7.6
```

```
$ mypy --version
mypy 0.770
```

  Do you see the same issue after installing mypy from Git master?

The error source code is still the same in Git master

* What are the mypy flags you are using? (For example --strict-optional)

No flags

* If mypy crashed with a traceback, please paste
  the full traceback below.

(You can freely edit this text, please remove all the lines
you believe are unnecessary.)



Discussion:

Resolving patch:
diff --git a/mypy/checkstrformat.py b/mypy/checkstrformat.py
--- a/mypy/checkstrformat.py
+++ b/mypy/checkstrformat.py
@@ -384,9 +384,10 @@ def perform_special_format_checks(self, spec: ConversionSpecifier, call: CallExp
             if self.chk.options.python_version >= (3, 0):
                 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'\";"
-                                  " use !r if this is a desired behavior", call,
-                                  code=codes.STR_BYTES_PY3)
+                    self.msg.fail(
+                        "On Python 3 '{}'.format(b'abc') produces \"b'abc'\", not 'abc'; "
+                        "use '{!r}'.format(b'abc') if this is desired behavior",
+                        call, code=codes.STR_BYTES_PY3)
         if spec.flags:
             numeric_types = UnionType([self.named_type('builtins.int'),
                                        self.named_type('builtins.float')])
@@ -843,9 +844,10 @@ def check_s_special_cases(self, expr: FormatStringExpr, typ: Type, context: Cont
             # Couple special cases for string formatting.
             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'\";"
-                                  " use %r if this is a desired behavior", context,
-                                  code=codes.STR_BYTES_PY3)
+                    self.msg.fail(
+                        "On Python 3 '%s' % b'abc' produces \"b'abc'\", not 'abc'; "
+                        "use '%r' % b'abc' if this is desired behavior",
+                        context, code=codes.STR_BYTES_PY3)
             if self.chk.options.python_version < (3, 0):
                 if has_type_component(typ, 'builtins.unicode'):
                     self.unicode_upcast = True


Test patch:
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
@@ -617,8 +617,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'"; use !r if this is a desired behavior  [str-bytes-safe]
-'%s' % b'abc'  # E: On Python 3 '%s' % b'abc' produces "b'abc'"; use %r if this is a desired behavior  [str-bytes-safe]
+'{}'.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]
 [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'"; use %r if this is a desired behavior
-'%(name)s' % {'name': b'value'}  # E: On Python 3 '%s' % b'abc' produces "b'abc'"; use %r if this is a desired behavior
+'%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
 [builtins fixtures/primitives.pyi]
 
 [case testStringInterpolationSBytesVsStrResultsPy2]
@@ -1576,17 +1576,17 @@ N = NewType('N', bytes)
 n: N
 
 '{}'.format(a)
-'{}'.format(b)  # E: On Python 3 '{}'.format(b'abc') produces "b'abc'"; use !r if this is a desired behavior
-'{}'.format(x)  # E: On Python 3 '{}'.format(b'abc') produces "b'abc'"; use !r if this is a desired behavior
-'{}'.format(n)  # E: On Python 3 '{}'.format(b'abc') produces "b'abc'"; use !r if this is a desired behavior
+'{}'.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
 
 class C(Generic[B]):
     x: B
     def meth(self) -> None:
-        '{}'.format(self.x)  # E: On Python 3 '{}'.format(b'abc') produces "b'abc'"; use !r if this is a desired behavior
+        '{}'.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
 
 def func(x: A) -> A:
-    '{}'.format(x)  # E: On Python 3 '{}'.format(b'abc') produces "b'abc'"; use !r if this is a 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
     return x
 
 '{!r}'.format(b)