mirror of
https://github.com/python/cpython.git
synced 2025-09-27 02:39:58 +00:00
bpo-28307: Tests and fixes for optimization of C-style formatting (GH-26318)
Fix errors: * "%10.s" should be equal to "%10.0s", not "%10s". * Tuples with starred expressions caused a SyntaxError.
This commit is contained in:
parent
bd7476dae3
commit
8b01067318
2 changed files with 95 additions and 8 deletions
|
@ -1,4 +1,5 @@
|
||||||
import dis
|
import dis
|
||||||
|
from itertools import combinations, product
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from test.support.bytecode_helper import BytecodeTestCase
|
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)])
|
return (y for x in a for y in [f(x)])
|
||||||
self.assertEqual(count_instr_recursively(genexpr, 'FOR_ITER'), 1)
|
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):
|
class TestBuglets(unittest.TestCase):
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,20 @@ make_const(expr_ty node, PyObject *val, PyArena *arena)
|
||||||
|
|
||||||
#define COPY_NODE(TO, FROM) (memcpy((TO), (FROM), sizeof(struct _expr)))
|
#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*
|
static PyObject*
|
||||||
unary_not(PyObject *v)
|
unary_not(PyObject *v)
|
||||||
{
|
{
|
||||||
|
@ -318,8 +332,8 @@ simple_format_arg_parse(PyObject *fmt, Py_ssize_t *ppos,
|
||||||
|
|
||||||
if (ch == '.') {
|
if (ch == '.') {
|
||||||
NEXTC;
|
NEXTC;
|
||||||
|
*prec = 0;
|
||||||
if ('0' <= ch && ch <= '9') {
|
if ('0' <= ch && ch <= '9') {
|
||||||
*prec = 0;
|
|
||||||
int digits = 0;
|
int digits = 0;
|
||||||
while ('0' <= ch && ch <= '9') {
|
while ('0' <= ch && ch <= '9') {
|
||||||
*prec = *prec * 10 + (ch - '0');
|
*prec = *prec * 10 + (ch - '0');
|
||||||
|
@ -445,7 +459,8 @@ fold_binop(expr_ty node, PyArena *arena, _PyASTOptimizeState *state)
|
||||||
|
|
||||||
if (node->v.BinOp.op == Mod &&
|
if (node->v.BinOp.op == Mod &&
|
||||||
rhs->kind == Tuple_kind &&
|
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);
|
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) {
|
if (arg->kind == List_kind) {
|
||||||
/* First change a list into tuple. */
|
/* First change a list into tuple. */
|
||||||
asdl_expr_seq *elts = arg->v.List.elts;
|
asdl_expr_seq *elts = arg->v.List.elts;
|
||||||
Py_ssize_t n = asdl_seq_LEN(elts);
|
if (has_starred(elts)) {
|
||||||
for (Py_ssize_t i = 0; i < n; i++) {
|
return 1;
|
||||||
expr_ty e = (expr_ty)asdl_seq_GET(elts, i);
|
|
||||||
if (e->kind == Starred_kind) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
expr_context_ty ctx = arg->v.List.ctx;
|
expr_context_ty ctx = arg->v.List.ctx;
|
||||||
arg->kind = Tuple_kind;
|
arg->kind = Tuple_kind;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue