py/objstr: Fix `str % {}` edge case.

Eliminate `TypeError` when format string contains no named conversions.
This matches CPython behavior.

Signed-off-by: mcskatkat <mc_skatkat@hotmail.com>
This commit is contained in:
mcskatkat 2023-07-11 00:50:33 +03:00 committed by Damien George
parent 83f2f36b9e
commit e0a1480600
2 changed files with 11 additions and 4 deletions

View File

@ -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"));
}

View File

@ -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: