From a2724095cd55d75497d9cd48d35911599f4dedda Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 8 Feb 2016 18:17:58 +0100 Subject: [PATCH] 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" --- Lib/test/test_ast.py | 33 +++++++++++------------ Lib/test/test_code.py | 58 +++++++++++++++++++++++++++------------- Lib/test/test_grammar.py | 6 ++++- Misc/NEWS | 4 +++ Python/compile.c | 41 ++++++++++++++++++++-------- 5 files changed, 95 insertions(+), 47 deletions(-) diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index f5c4adeb6f8..dbcd9f74ffa 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -3,6 +3,7 @@ import dis import os import sys import unittest +import warnings import weakref from test import support @@ -239,8 +240,10 @@ class AST_Tests(unittest.TestCase): ast_tree = compile(i, "?", kind, ast.PyCF_ONLY_AST) self.assertEqual(to_tuple(ast_tree), o) self._assertTrueorder(ast_tree, (0, 0)) - with self.subTest(action="compiling", input=i): - compile(ast_tree, "?", kind) + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', category=SyntaxWarning) + with self.subTest(action="compiling", input=i, kind=kind): + compile(ast_tree, "?", kind) def test_slice(self): slc = ast.parse("x[::]").body[0].value.slice @@ -1020,27 +1023,23 @@ class ConstantTests(unittest.TestCase): b'bytes', (1, 2, 3)] - code = '\n'.join(map(repr, consts)) - code += '\n...' - - 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) + code = '\n'.join(['x={!r}'.format(const) for const in consts]) + code += '\nx = ...' + consts.extend((Ellipsis, None)) 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 - for expr_node, const in zip(tree.body, consts): - assert isinstance(expr_node, ast.Expr) + for assign, const in zip(tree.body, consts): + assert isinstance(assign, ast.Assign), ast.dump(assign) new_node = ast.Constant(value=const) - ast.copy_location(new_node, expr_node.value) - expr_node.value = new_node + ast.copy_location(new_node, assign.value) + 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): tree = ast.parse("1 + 2") diff --git a/Lib/test/test_code.py b/Lib/test/test_code.py index 21b12a56e4a..2cf088ce3ca 100644 --- a/Lib/test/test_code.py +++ b/Lib/test/test_code.py @@ -66,24 +66,6 @@ nlocals: 1 flags: 67 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): ... return a,b,k1 ... @@ -102,8 +84,10 @@ consts: ('None',) """ +import textwrap import unittest import weakref +import warnings 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_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): diff --git a/Lib/test/test_grammar.py b/Lib/test/test_grammar.py index 8f8d71ce85e..2d6f5edf05c 100644 --- a/Lib/test/test_grammar.py +++ b/Lib/test/test_grammar.py @@ -7,6 +7,7 @@ import unittest import sys # testing import * from sys import * +from test import support class TokenTests(unittest.TestCase): @@ -424,8 +425,11 @@ class GrammarTests(unittest.TestCase): # Tested below def test_expr_stmt(self): + msg = 'ignore constant statement' + with support.check_warnings((msg, SyntaxWarning)): + exec("1") + # (exprlist '=')* exprlist - 1 1, 2, 3 x = 1 x = 1, 2, 3 diff --git a/Misc/NEWS b/Misc/NEWS index 0c7d18ec291..e1875c0e991 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -10,6 +10,10 @@ Release date: tba 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 (*) unpacking in function calls. Based on patch by Hagen Fürstenau and Daniel Urban. diff --git a/Python/compile.c b/Python/compile.c index ccb05cf1ac8..84b79a2fffd 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -2616,20 +2616,39 @@ compiler_visit_stmt_expr(struct compiler *c, expr_ty value) return 1; } - if (value->kind == Str_kind || value->kind == Num_kind) { - /* ignore strings and numbers */ + switch (value->kind) + { + 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; } - if (value->kind == Constant_kind) { - PyObject *cst = value->v.Constant.value; - if (PyUnicode_CheckExact(cst) - || PyLong_CheckExact(cst) - || PyFloat_CheckExact(cst) - || PyComplex_CheckExact(cst)) { - /* ignore strings and numbers */ - return 1; - } + default: + break; } VISIT(c, expr, value);