]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-28307: Tests and fixes for optimization of C-style formatting (GH-26318)
authorSerhiy Storchaka <storchaka@gmail.com>
Sun, 23 May 2021 16:06:48 +0000 (19:06 +0300)
committerGitHub <noreply@github.com>
Sun, 23 May 2021 16:06:48 +0000 (19:06 +0300)
Fix errors:
* "%10.s" should be equal to "%10.0s", not "%10s".
* Tuples with starred expressions caused a SyntaxError.

Lib/test/test_peepholer.py
Python/ast_opt.c

index 4034154e4dcfb591f7335b95a95d3750cad57fdc..bfafc3ee4b9542b12e70b33230b57589770aa893 100644 (file)
@@ -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):
 
index 53e089cfab499c30a7af5428e02ae75cfd5fdfdd..f6506cef035cd43bb7e96f93b1e0ab7e806187cd 100644 (file)
@@ -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;