Skip to content
9 changes: 7 additions & 2 deletions mypy/checkstrformat.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import mypy.errorcodes as codes
from mypy import message_registry
from mypy.checker_shared import TypeCheckerSharedApi
from mypy.constant_fold import constant_fold_expr
from mypy.errors import Errors
from mypy.maptype import map_instance_to_supertype
from mypy.messages import MessageBuilder
Expand Down Expand Up @@ -1005,8 +1006,12 @@ def check_expr(expr: Expression) -> None:
and len(expr.value) != 1
):
self.msg.requires_int_or_single_byte(context)
elif isinstance(expr, (StrExpr, BytesExpr)) and len(expr.value) != 1:
self.msg.requires_int_or_char(context)
elif isinstance(folded := constant_fold_expr(expr, "<unused>"), str):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note to self: can I get cur_mod_id from context?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to pass cur_mod_id here for this PR to work properly. I'm just not entirely sure how we can get access to it here.

if len(folded) != 1:
self.msg.requires_int_or_char(context)
elif isinstance(expr, BytesExpr):
if len(expr.value) != 1:
self.msg.requires_int_or_char(context)

return check_expr, check_type

Expand Down
25 changes: 19 additions & 6 deletions test-data/unit/check-formatting.test
Original file line number Diff line number Diff line change
Expand Up @@ -103,14 +103,27 @@ a = None # type: Any
[typing fixtures/typing-medium.pyi]

[case testStringInterpolationC]
from typing import Final

final_int: Final = 1
final_float: Final = 1.0
final_char_string: Final = 's'
final_empty_string: Final = ''
final_string: Final = 'ab'

'%c' % 1
'%c' % 1.0 # E: "%c" requires int or char (expression has type "float")
'%c' % final_int
'%c' % 1.0 # E: "%c" requires int or char (expression has type "float")
'%c' % final_float # E: "%c" requires int or char (expression has type "float")
'%c' % 's'
'%c' % '' # E: "%c" requires int or char
'%c' % 'ab' # E: "%c" requires int or char
'%c' % b'a' # E: "%c" requires int or char (expression has type "bytes")
'%c' % b'' # E: "%c" requires int or char (expression has type "bytes")
'%c' % b'ab' # E: "%c" requires int or char (expression has type "bytes")
'%c' % final_char_string
'%c' % '' # E: "%c" requires int or char
'%c' % final_empty_string # E: "%c" requires int or char
'%c' % 'ab' # E: "%c" requires int or char
'%c' % final_string # E: "%c" requires int or char
'%c' % b'a' # E: "%c" requires int or char (expression has type "bytes")
'%c' % b'' # E: "%c" requires int or char (expression has type "bytes")
'%c' % b'ab' # E: "%c" requires int or char (expression has type "bytes")
[builtins fixtures/primitives.pyi]

[case testStringInterpolationMappingTypes]
Expand Down
Loading