From e0a148060025a75f4051e5009d9dc3728cb2bc75 Mon Sep 17 00:00:00 2001 From: mcskatkat Date: Tue, 11 Jul 2023 00:50:33 +0300 Subject: [PATCH] py/objstr: Fix `str % {}` edge case. Eliminate `TypeError` when format string contains no named conversions. This matches CPython behavior. Signed-off-by: mcskatkat --- py/objstr.c | 4 +++- tests/basics/string_format_modulo.py | 11 ++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/py/objstr.c b/py/objstr.c index b966a7016..5dfe94ac4 100644 --- a/py/objstr.c +++ b/py/objstr.c @@ -1645,7 +1645,9 @@ STATIC mp_obj_t str_modulo_format(mp_obj_t pattern, size_t n_args, const mp_obj_ } } - if (arg_i != n_args) { + if (dict == MP_OBJ_NULL && arg_i != n_args) { + // NOTE: if `dict` exists, then `n_args` is 1 and the dict is always consumed; either + // positionally, or as a map of named args, even if none were actually referenced. mp_raise_TypeError(MP_ERROR_TEXT("format string didn't convert all arguments")); } diff --git a/tests/basics/string_format_modulo.py b/tests/basics/string_format_modulo.py index 14b4a6a48..7bddd9675 100644 --- a/tests/basics/string_format_modulo.py +++ b/tests/basics/string_format_modulo.py @@ -51,8 +51,9 @@ print('%c' % True) # Should be able to print dicts; in this case they aren't used # to lookup keywords in formats like %(foo)s -print('%s' % {}) -print('%s' % ({},)) +print('%s' % {}) # dict treated as the single (positional) arg to % +print('%s' % ({},)) # dict is the first (and only) arg in the positional arg tuple +print('foo' % {}) # no error, dict treated as an empty map of named args # Cases when "*" used and there's not enough values total try: @@ -65,7 +66,11 @@ except TypeError: print("TypeError") print("%(foo)s" % {"foo": "bar", "baz": False}) -print("%s %(foo)s %(foo)s" % {"foo": 1}) +print("%s %(foo)s %(foo)s" % {"foo": 1}) # dict consumed positionally, then used as map - ok +try: + print("%(foo)s %s %(foo)s" % {"foo": 1}) # used as map, then positionally - not enough args +except TypeError: + print("TypeError") try: print("%(foo)s" % {}) except KeyError: