Skip to content

Commit 732faf1

Browse files
authored
gh-115347: avoid emitting redundant NOP for the docstring with -OO (#115494)
1 parent ad4f909 commit 732faf1

File tree

3 files changed

+48
-18
lines changed

3 files changed

+48
-18
lines changed

Lib/test/test_compile.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import contextlib
12
import dis
3+
import io
24
import math
35
import os
46
import unittest
@@ -812,6 +814,30 @@ def unused_code_at_end():
812814
'RETURN_CONST',
813815
list(dis.get_instructions(unused_code_at_end))[-1].opname)
814816

817+
@support.cpython_only
818+
def test_docstring_omitted(self):
819+
# See gh-115347
820+
src = textwrap.dedent("""
821+
def f():
822+
"docstring1"
823+
def h():
824+
"docstring2"
825+
return 42
826+
827+
class C:
828+
"docstring3"
829+
pass
830+
831+
return h
832+
""")
833+
for opt in [-1, 0, 1, 2]:
834+
with self.subTest(opt=opt):
835+
code = compile(src, "<test>", "exec", optimize=opt)
836+
output = io.StringIO()
837+
with contextlib.redirect_stdout(output):
838+
dis.dis(code)
839+
self.assertNotIn('NOP' , output.getvalue())
840+
815841
def test_dont_merge_constants(self):
816842
# Issue #25843: compile() must not merge constants which are equal
817843
# but have a different type.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix bug where docstring was replaced by a redundant NOP when Python is run
2+
with ``-OO``.

Python/compile.c

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1692,16 +1692,13 @@ compiler_unwind_fblock_stack(struct compiler *c, location *ploc,
16921692
static int
16931693
compiler_body(struct compiler *c, location loc, asdl_stmt_seq *stmts)
16941694
{
1695-
int i = 0;
1696-
stmt_ty st;
1697-
PyObject *docstring;
16981695

16991696
/* Set current line number to the line number of first statement.
17001697
This way line number for SETUP_ANNOTATIONS will always
17011698
coincide with the line number of first "real" statement in module.
17021699
If body is empty, then lineno will be set later in optimize_and_assemble. */
17031700
if (c->u->u_scope_type == COMPILER_SCOPE_MODULE && asdl_seq_LEN(stmts)) {
1704-
st = (stmt_ty)asdl_seq_GET(stmts, 0);
1701+
stmt_ty st = (stmt_ty)asdl_seq_GET(stmts, 0);
17051702
loc = LOC(st);
17061703
}
17071704
/* Every annotated class and module should have __annotations__. */
@@ -1711,24 +1708,25 @@ compiler_body(struct compiler *c, location loc, asdl_stmt_seq *stmts)
17111708
if (!asdl_seq_LEN(stmts)) {
17121709
return SUCCESS;
17131710
}
1714-
/* if not -OO mode, set docstring */
1715-
if (c->c_optimize < 2) {
1716-
docstring = _PyAST_GetDocString(stmts);
1717-
if (docstring) {
1711+
Py_ssize_t first_instr = 0;
1712+
PyObject *docstring = _PyAST_GetDocString(stmts);
1713+
if (docstring) {
1714+
first_instr = 1;
1715+
/* if not -OO mode, set docstring */
1716+
if (c->c_optimize < 2) {
17181717
PyObject *cleandoc = _PyCompile_CleanDoc(docstring);
17191718
if (cleandoc == NULL) {
17201719
return ERROR;
17211720
}
1722-
i = 1;
1723-
st = (stmt_ty)asdl_seq_GET(stmts, 0);
1721+
stmt_ty st = (stmt_ty)asdl_seq_GET(stmts, 0);
17241722
assert(st->kind == Expr_kind);
17251723
location loc = LOC(st->v.Expr.value);
17261724
ADDOP_LOAD_CONST(c, loc, cleandoc);
17271725
Py_DECREF(cleandoc);
17281726
RETURN_IF_ERROR(compiler_nameop(c, NO_LOCATION, &_Py_ID(__doc__), Store));
17291727
}
17301728
}
1731-
for (; i < asdl_seq_LEN(stmts); i++) {
1729+
for (Py_ssize_t i = first_instr; i < asdl_seq_LEN(stmts); i++) {
17321730
VISIT(c, stmt, (stmt_ty)asdl_seq_GET(stmts, i));
17331731
}
17341732
return SUCCESS;
@@ -2239,7 +2237,6 @@ static int
22392237
compiler_function_body(struct compiler *c, stmt_ty s, int is_async, Py_ssize_t funcflags,
22402238
int firstlineno)
22412239
{
2242-
PyObject *docstring = NULL;
22432240
arguments_ty args;
22442241
identifier name;
22452242
asdl_stmt_seq *body;
@@ -2266,28 +2263,33 @@ compiler_function_body(struct compiler *c, stmt_ty s, int is_async, Py_ssize_t f
22662263
RETURN_IF_ERROR(
22672264
compiler_enter_scope(c, name, scope_type, (void *)s, firstlineno));
22682265

2269-
/* if not -OO mode, add docstring */
2270-
if (c->c_optimize < 2) {
2271-
docstring = _PyAST_GetDocString(body);
2272-
if (docstring) {
2266+
Py_ssize_t first_instr = 0;
2267+
PyObject *docstring = _PyAST_GetDocString(body);
2268+
if (docstring) {
2269+
first_instr = 1;
2270+
/* if not -OO mode, add docstring */
2271+
if (c->c_optimize < 2) {
22732272
docstring = _PyCompile_CleanDoc(docstring);
22742273
if (docstring == NULL) {
22752274
compiler_exit_scope(c);
22762275
return ERROR;
22772276
}
22782277
}
2278+
else {
2279+
docstring = NULL;
2280+
}
22792281
}
22802282
if (compiler_add_const(c->c_const_cache, c->u, docstring ? docstring : Py_None) < 0) {
22812283
Py_XDECREF(docstring);
22822284
compiler_exit_scope(c);
22832285
return ERROR;
22842286
}
2285-
Py_XDECREF(docstring);
2287+
Py_CLEAR(docstring);
22862288

22872289
c->u->u_metadata.u_argcount = asdl_seq_LEN(args->args);
22882290
c->u->u_metadata.u_posonlyargcount = asdl_seq_LEN(args->posonlyargs);
22892291
c->u->u_metadata.u_kwonlyargcount = asdl_seq_LEN(args->kwonlyargs);
2290-
for (Py_ssize_t i = docstring ? 1 : 0; i < asdl_seq_LEN(body); i++) {
2292+
for (Py_ssize_t i = first_instr; i < asdl_seq_LEN(body); i++) {
22912293
VISIT_IN_SCOPE(c, stmt, (stmt_ty)asdl_seq_GET(body, i));
22922294
}
22932295
if (c->u->u_ste->ste_coroutine || c->u->u_ste->ste_generator) {

0 commit comments

Comments
 (0)