summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/test/test_peepholer.py76
-rw-r--r--Python/ast_opt.c27
2 files changed, 95 insertions, 8 deletions
diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py
index 4034154..bfafc3e 100644
--- a/Lib/test/test_peepholer.py
+++ b/Lib/test/test_peepholer.py
@@ -1,4 +1,5 @@
import dis
+from itertools import combinations, product
import unittest
from test.support.bytecode_helper import BytecodeTestCase
@@ -494,6 +495,81 @@ class TestTranforms(BytecodeTestCase):
return (y for x in a for y in [f(x)])
self.assertEqual(count_instr_recursively(genexpr, 'FOR_ITER'), 1)
+ def test_format_combinations(self):
+ flags = '-+ #0'
+ testcases = [
+ *product(('', '1234', 'абвг'), 'sra'),
+ *product((1234, -1234), 'duioxX'),
+ *product((1234.5678901, -1234.5678901), 'duifegFEG'),
+ *product((float('inf'), -float('inf')), 'fegFEG'),
+ ]
+ width_precs = [
+ *product(('', '1', '30'), ('', '.', '.0', '.2')),
+ ('', '.40'),
+ ('30', '.40'),
+ ]
+ for value, suffix in testcases:
+ for width, prec in width_precs:
+ for r in range(len(flags) + 1):
+ for spec in combinations(flags, r):
+ fmt = '%' + ''.join(spec) + width + prec + suffix
+ with self.subTest(fmt=fmt, value=value):
+ s1 = fmt % value
+ s2 = eval(f'{fmt!r} % (x,)', {'x': value})
+ self.assertEqual(s2, s1, f'{fmt = }')
+
+ def test_format_misc(self):
+ def format(fmt, *values):
+ vars = [f'x{i+1}' for i in range(len(values))]
+ if len(vars) == 1:
+ args = '(' + vars[0] + ',)'
+ else:
+ args = '(' + ', '.join(vars) + ')'
+ return eval(f'{fmt!r} % {args}', dict(zip(vars, values)))
+
+ self.assertEqual(format('string'), 'string')
+ self.assertEqual(format('x = %s!', 1234), 'x = 1234!')
+ self.assertEqual(format('x = %d!', 1234), 'x = 1234!')
+ self.assertEqual(format('x = %x!', 1234), 'x = 4d2!')
+ self.assertEqual(format('x = %f!', 1234), 'x = 1234.000000!')
+ self.assertEqual(format('x = %s!', 1234.5678901), 'x = 1234.5678901!')
+ self.assertEqual(format('x = %f!', 1234.5678901), 'x = 1234.567890!')
+ self.assertEqual(format('x = %d!', 1234.5678901), 'x = 1234!')
+ self.assertEqual(format('x = %s%% %%%%', 1234), 'x = 1234% %%')
+ self.assertEqual(format('x = %s!', '%% %s'), 'x = %% %s!')
+ self.assertEqual(format('x = %s, y = %d', 12, 34), 'x = 12, y = 34')
+
+ def test_format_errors(self):
+ with self.assertRaisesRegex(TypeError,
+ 'not enough arguments for format string'):
+ eval("'%s' % ()")
+ with self.assertRaisesRegex(TypeError,
+ 'not all arguments converted during string formatting'):
+ eval("'%s' % (x, y)", {'x': 1, 'y': 2})
+ with self.assertRaisesRegex(ValueError, 'incomplete format'):
+ eval("'%s%' % (x,)", {'x': 1234})
+ with self.assertRaisesRegex(ValueError, 'incomplete format'):
+ eval("'%s%%%' % (x,)", {'x': 1234})
+ with self.assertRaisesRegex(TypeError,
+ 'not enough arguments for format string'):
+ eval("'%s%z' % (x,)", {'x': 1234})
+ with self.assertRaisesRegex(ValueError, 'unsupported format character'):
+ eval("'%s%z' % (x, 5)", {'x': 1234})
+ with self.assertRaisesRegex(TypeError, 'a real number is required, not str'):
+ eval("'%d' % (x,)", {'x': '1234'})
+ with self.assertRaisesRegex(TypeError, 'an integer is required, not float'):
+ eval("'%x' % (x,)", {'x': 1234.56})
+ with self.assertRaisesRegex(TypeError, 'an integer is required, not str'):
+ eval("'%x' % (x,)", {'x': '1234'})
+ with self.assertRaisesRegex(TypeError, 'must be real number, not str'):
+ eval("'%f' % (x,)", {'x': '1234'})
+ with self.assertRaisesRegex(TypeError,
+ 'not enough arguments for format string'):
+ eval("'%s, %s' % (x, *y)", {'x': 1, 'y': []})
+ with self.assertRaisesRegex(TypeError,
+ 'not all arguments converted during string formatting'):
+ eval("'%s, %s' % (x, *y)", {'x': 1, 'y': [2, 3]})
+
class TestBuglets(unittest.TestCase):
diff --git a/Python/ast_opt.c b/Python/ast_opt.c
index 53e089c..f6506ce 100644
--- a/Python/ast_opt.c
+++ b/Python/ast_opt.c
@@ -30,6 +30,20 @@ make_const(expr_ty node, PyObject *val, PyArena *arena)
#define COPY_NODE(TO, FROM) (memcpy((TO), (FROM), sizeof(struct _expr)))
+static int
+has_starred(asdl_expr_seq *elts)
+{
+ Py_ssize_t n = asdl_seq_LEN(elts);
+ for (Py_ssize_t i = 0; i < n; i++) {
+ expr_ty e = (expr_ty)asdl_seq_GET(elts, i);
+ if (e->kind == Starred_kind) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
static PyObject*
unary_not(PyObject *v)
{
@@ -318,8 +332,8 @@ simple_format_arg_parse(PyObject *fmt, Py_ssize_t *ppos,
if (ch == '.') {
NEXTC;
+ *prec = 0;
if ('0' <= ch && ch <= '9') {
- *prec = 0;
int digits = 0;
while ('0' <= ch && ch <= '9') {
*prec = *prec * 10 + (ch - '0');
@@ -445,7 +459,8 @@ fold_binop(expr_ty node, PyArena *arena, _PyASTOptimizeState *state)
if (node->v.BinOp.op == Mod &&
rhs->kind == Tuple_kind &&
- PyUnicode_Check(lv))
+ PyUnicode_Check(lv) &&
+ !has_starred(rhs->v.Tuple.elts))
{
return optimize_format(node, lv, rhs->v.Tuple.elts, arena);
}
@@ -572,12 +587,8 @@ fold_iter(expr_ty arg, PyArena *arena, _PyASTOptimizeState *state)
if (arg->kind == List_kind) {
/* First change a list into tuple. */
asdl_expr_seq *elts = arg->v.List.elts;
- Py_ssize_t n = asdl_seq_LEN(elts);
- for (Py_ssize_t i = 0; i < n; i++) {
- expr_ty e = (expr_ty)asdl_seq_GET(elts, i);
- if (e->kind == Starred_kind) {
- return 1;
- }
+ if (has_starred(elts)) {
+ return 1;
}
expr_context_ty ctx = arg->v.List.ctx;
arg->kind = Tuple_kind;