From 30d454d3af8cfefe1ccc59dd39a46651cb3b2706 Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Thu, 2 Oct 2025 01:01:52 +0000 Subject: [PATCH 1/5] [mypyc] feat: support constant folding in `StringFormatterChecker.checkers_for_c_type` --- mypy/checkstrformat.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/mypy/checkstrformat.py b/mypy/checkstrformat.py index 45075bd37552..c77b35a278a5 100644 --- a/mypy/checkstrformat.py +++ b/mypy/checkstrformat.py @@ -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 @@ -1005,8 +1006,13 @@ 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) + else: + if isinstance(folded := constant_fold_expr(expr, "unused"), str): + value = folded + elif isinstance(expr, BytesExpr): + value = expr.value + if len(value) != 1: + self.msg.requires_int_or_char(context) return check_expr, check_type From d30b2b5ed19234ea6bbd85dec54221580ed5a430 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Wed, 1 Oct 2025 21:31:29 -0400 Subject: [PATCH 2/5] Update checkstrformat.py --- mypy/checkstrformat.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mypy/checkstrformat.py b/mypy/checkstrformat.py index c77b35a278a5..72d82ac3a2c9 100644 --- a/mypy/checkstrformat.py +++ b/mypy/checkstrformat.py @@ -1008,11 +1008,11 @@ def check_expr(expr: Expression) -> None: self.msg.requires_int_or_single_byte(context) else: if isinstance(folded := constant_fold_expr(expr, "unused"), str): - value = folded + if len(folded) != 1: + self.msg.requires_int_or_char(context) elif isinstance(expr, BytesExpr): - value = expr.value - if len(value) != 1: - self.msg.requires_int_or_char(context) + if len(expr.value) != 1: + self.msg.requires_int_or_char(context) return check_expr, check_type From cb38f60e19d0312b478d42768104497137721972 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Wed, 1 Oct 2025 21:42:52 -0400 Subject: [PATCH 3/5] Update checkstrformat.py --- mypy/checkstrformat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/checkstrformat.py b/mypy/checkstrformat.py index 72d82ac3a2c9..35a77a94a9c7 100644 --- a/mypy/checkstrformat.py +++ b/mypy/checkstrformat.py @@ -1007,7 +1007,7 @@ def check_expr(expr: Expression) -> None: ): self.msg.requires_int_or_single_byte(context) else: - if isinstance(folded := constant_fold_expr(expr, "unused"), str): + if isinstance(folded := constant_fold_expr(expr, ""), str): if len(folded) != 1: self.msg.requires_int_or_char(context) elif isinstance(expr, BytesExpr): From 65489950c1baf544aaba89d7250c5595c7e64a89 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Thu, 2 Oct 2025 02:54:40 -0400 Subject: [PATCH 4/5] Update checkstrformat.py --- mypy/checkstrformat.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/mypy/checkstrformat.py b/mypy/checkstrformat.py index 35a77a94a9c7..4cbbc86d1236 100644 --- a/mypy/checkstrformat.py +++ b/mypy/checkstrformat.py @@ -1006,13 +1006,12 @@ def check_expr(expr: Expression) -> None: and len(expr.value) != 1 ): self.msg.requires_int_or_single_byte(context) - else: - if isinstance(folded := constant_fold_expr(expr, ""), str): - 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) + elif isinstance(folded := constant_fold_expr(expr, ""), str): + 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 From 44e935385d5a2c8a6792bf32f8b4efc772584870 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Fri, 3 Oct 2025 22:09:30 -0400 Subject: [PATCH 5/5] add check tests --- test-data/unit/check-formatting.test | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/test-data/unit/check-formatting.test b/test-data/unit/check-formatting.test index b5b37f8d2976..374f0dcc1a52 100644 --- a/test-data/unit/check-formatting.test +++ b/test-data/unit/check-formatting.test @@ -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]