compiler now ignores constant statements

The compile ignores constant statements and emit a SyntaxWarning warning.

Don't emit the warning for string statement because triple quoted string is a
common syntax for multiline comments.

Don't emit the warning on ellipis neither: 'def f(): ...' is a legit syntax for
abstract functions.

Changes:

* test_ast: ignore SyntaxWarning when compiling test statements. Modify
  test_load_const() to use assignment expressions rather than constant
  expression.
* test_code: add more kinds of constant statements, ignore SyntaxWarning when
  testing that the compiler removes constant statements.
* test_grammar: ignore SyntaxWarning on the statement "1"
This commit is contained in:
Victor Stinner 2016-02-08 18:17:58 +01:00
parent 51d8c526d5
commit a2724095cd
5 changed files with 95 additions and 47 deletions

View File

@ -3,6 +3,7 @@ import dis
import os import os
import sys import sys
import unittest import unittest
import warnings
import weakref import weakref
from test import support from test import support
@ -239,7 +240,9 @@ class AST_Tests(unittest.TestCase):
ast_tree = compile(i, "?", kind, ast.PyCF_ONLY_AST) ast_tree = compile(i, "?", kind, ast.PyCF_ONLY_AST)
self.assertEqual(to_tuple(ast_tree), o) self.assertEqual(to_tuple(ast_tree), o)
self._assertTrueorder(ast_tree, (0, 0)) self._assertTrueorder(ast_tree, (0, 0))
with self.subTest(action="compiling", input=i): with warnings.catch_warnings():
warnings.filterwarnings('ignore', category=SyntaxWarning)
with self.subTest(action="compiling", input=i, kind=kind):
compile(ast_tree, "?", kind) compile(ast_tree, "?", kind)
def test_slice(self): def test_slice(self):
@ -1020,27 +1023,23 @@ class ConstantTests(unittest.TestCase):
b'bytes', b'bytes',
(1, 2, 3)] (1, 2, 3)]
code = '\n'.join(map(repr, consts)) code = '\n'.join(['x={!r}'.format(const) for const in consts])
code += '\n...' code += '\nx = ...'
consts.extend((Ellipsis, None))
code_consts = [const for const in consts
if (not isinstance(const, (str, int, float, complex))
or isinstance(const, bool))]
code_consts.append(Ellipsis)
# the compiler adds a final "LOAD_CONST None"
code_consts.append(None)
tree = ast.parse(code) tree = ast.parse(code)
self.assertEqual(self.get_load_const(tree), code_consts) self.assertEqual(self.get_load_const(tree),
consts)
# Replace expression nodes with constants # Replace expression nodes with constants
for expr_node, const in zip(tree.body, consts): for assign, const in zip(tree.body, consts):
assert isinstance(expr_node, ast.Expr) assert isinstance(assign, ast.Assign), ast.dump(assign)
new_node = ast.Constant(value=const) new_node = ast.Constant(value=const)
ast.copy_location(new_node, expr_node.value) ast.copy_location(new_node, assign.value)
expr_node.value = new_node assign.value = new_node
self.assertEqual(self.get_load_const(tree), code_consts) self.assertEqual(self.get_load_const(tree),
consts)
def test_literal_eval(self): def test_literal_eval(self):
tree = ast.parse("1 + 2") tree = ast.parse("1 + 2")

View File

@ -66,24 +66,6 @@ nlocals: 1
flags: 67 flags: 67
consts: ('None',) consts: ('None',)
>>> def optimize_away():
... 'doc string'
... 'not a docstring'
... 53
... 0x53
>>> dump(optimize_away.__code__)
name: optimize_away
argcount: 0
kwonlyargcount: 0
names: ()
varnames: ()
cellvars: ()
freevars: ()
nlocals: 0
flags: 67
consts: ("'doc string'", 'None')
>>> def keywordonly_args(a,b,*,k1): >>> def keywordonly_args(a,b,*,k1):
... return a,b,k1 ... return a,b,k1
... ...
@ -102,8 +84,10 @@ consts: ('None',)
""" """
import textwrap
import unittest import unittest
import weakref import weakref
import warnings
from test.support import run_doctest, run_unittest, cpython_only from test.support import run_doctest, run_unittest, cpython_only
@ -134,6 +118,44 @@ class CodeTest(unittest.TestCase):
self.assertEqual(co.co_name, "funcname") self.assertEqual(co.co_name, "funcname")
self.assertEqual(co.co_firstlineno, 15) self.assertEqual(co.co_firstlineno, 15)
def dump(self, co):
dump = {}
for attr in ["name", "argcount", "kwonlyargcount", "names", "varnames",
"cellvars", "freevars", "nlocals", "flags"]:
dump[attr] = getattr(co, "co_" + attr)
dump['consts'] = tuple(consts(co.co_consts))
return dump
def test_optimize_away(self):
ns = {}
with warnings.catch_warnings():
warnings.filterwarnings('ignore', category=SyntaxWarning)
exec(textwrap.dedent('''
def optimize_away():
'doc string'
'not a docstring'
53
0x53
b'bytes'
1.0
True
False
None
...
'''), ns)
self.assertEqual(self.dump(ns['optimize_away'].__code__),
{'name': 'optimize_away',
'argcount': 0,
'kwonlyargcount': 0,
'names': (),
'varnames': (),
'cellvars': (),
'freevars': (),
'nlocals': 0,
'flags': 67,
'consts': ("'doc string'", 'None')})
class CodeWeakRefTest(unittest.TestCase): class CodeWeakRefTest(unittest.TestCase):

View File

@ -7,6 +7,7 @@ import unittest
import sys import sys
# testing import * # testing import *
from sys import * from sys import *
from test import support
class TokenTests(unittest.TestCase): class TokenTests(unittest.TestCase):
@ -424,8 +425,11 @@ class GrammarTests(unittest.TestCase):
# Tested below # Tested below
def test_expr_stmt(self): def test_expr_stmt(self):
msg = 'ignore constant statement'
with support.check_warnings((msg, SyntaxWarning)):
exec("1")
# (exprlist '=')* exprlist # (exprlist '=')* exprlist
1
1, 2, 3 1, 2, 3
x = 1 x = 1
x = 1, 2, 3 x = 1, 2, 3

View File

@ -10,6 +10,10 @@ Release date: tba
Core and Builtins Core and Builtins
----------------- -----------------
- Issue #26204: The compiler now ignores constant statements (ex: "def f(): 1")
and emit a SyntaxWarning warning. The warning is not emitted for string and
ellipsis (...) statements.
- Issue #4806: Avoid masking the original TypeError exception when using star - Issue #4806: Avoid masking the original TypeError exception when using star
(*) unpacking in function calls. Based on patch by Hagen Fürstenau and (*) unpacking in function calls. Based on patch by Hagen Fürstenau and
Daniel Urban. Daniel Urban.

View File

@ -2616,20 +2616,39 @@ compiler_visit_stmt_expr(struct compiler *c, expr_ty value)
return 1; return 1;
} }
if (value->kind == Str_kind || value->kind == Num_kind) { switch (value->kind)
/* ignore strings and numbers */ {
case Str_kind:
case Ellipsis_kind:
/* Issue #26204: ignore string statement, but don't emit a
* SyntaxWarning. Triple quoted strings is a common syntax for
* multiline comments.
*
* Don't emit warning on "def f(): ..." neither. It's a legit syntax
* for abstract function. */
return 1;
case Bytes_kind:
case Num_kind:
case NameConstant_kind:
case Constant_kind:
{
PyObject *msg = PyUnicode_FromString("ignore constant statement");
if (msg == NULL)
return 0;
if (PyErr_WarnExplicitObject(PyExc_SyntaxWarning,
msg,
c->c_filename, c->u->u_lineno,
NULL, NULL) == -1) {
Py_DECREF(msg);
return 0;
}
Py_DECREF(msg);
return 1; return 1;
} }
if (value->kind == Constant_kind) { default:
PyObject *cst = value->v.Constant.value; break;
if (PyUnicode_CheckExact(cst)
|| PyLong_CheckExact(cst)
|| PyFloat_CheckExact(cst)
|| PyComplex_CheckExact(cst)) {
/* ignore strings and numbers */
return 1;
}
} }
VISIT(c, expr, value); VISIT(c, expr, value);