Skip to content

Commit a9594a3

Browse files
gh-124022: Fix bug where class docstring is removed in interactive mode (#124023)
Co-authored-by: Jelle Zijlstra <[email protected]>
1 parent cfe6074 commit a9594a3

File tree

5 files changed

+37
-12
lines changed

5 files changed

+37
-12
lines changed

Include/internal/pycore_compile.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,8 @@ int _PyCompile_AddDeferredAnnotaion(struct _PyCompiler *c, stmt_ty s);
172172
int _PyCodegen_AddReturnAtEnd(struct _PyCompiler *c, int addNone);
173173
int _PyCodegen_EnterAnonymousScope(struct _PyCompiler* c, mod_ty mod);
174174
int _PyCodegen_Expression(struct _PyCompiler *c, expr_ty e);
175-
int _PyCodegen_Body(struct _PyCompiler *c, _Py_SourceLocation loc, asdl_stmt_seq *stmts);
175+
int _PyCodegen_Body(struct _PyCompiler *c, _Py_SourceLocation loc, asdl_stmt_seq *stmts,
176+
bool is_interactive);
176177

177178
/* Utility for a number of growing arrays used in the compiler */
178179
int _PyCompile_EnsureArrayLargeEnough(

Lib/test/test_compile.py

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -902,6 +902,28 @@ def with_const_expression():
902902
self.assertIsNone(ns['with_fstring'].__doc__)
903903
self.assertIsNone(ns['with_const_expression'].__doc__)
904904

905+
@support.cpython_only
906+
def test_docstring_interactive_mode(self):
907+
srcs = [
908+
"""def with_docstring():
909+
"docstring"
910+
""",
911+
"""class with_docstring:
912+
"docstring"
913+
""",
914+
]
915+
916+
for opt in [0, 1, 2]:
917+
for src in srcs:
918+
with self.subTest(opt=opt, src=src):
919+
code = compile(textwrap.dedent(src), "<test>", "single", optimize=opt)
920+
ns = {}
921+
exec(code, ns)
922+
if opt < 2:
923+
self.assertEqual(ns['with_docstring'].__doc__, "docstring")
924+
else:
925+
self.assertIsNone(ns['with_docstring'].__doc__)
926+
905927
@support.cpython_only
906928
def test_docstring_omitted(self):
907929
# See gh-115347
@@ -919,12 +941,13 @@ class C:
919941
return h
920942
""")
921943
for opt in [-1, 0, 1, 2]:
922-
with self.subTest(opt=opt):
923-
code = compile(src, "<test>", "exec", optimize=opt)
924-
output = io.StringIO()
925-
with contextlib.redirect_stdout(output):
926-
dis.dis(code)
927-
self.assertNotIn('NOP' , output.getvalue())
944+
for mode in ["exec", "single"]:
945+
with self.subTest(opt=opt, mode=mode):
946+
code = compile(src, "<test>", mode, optimize=opt)
947+
output = io.StringIO()
948+
with contextlib.redirect_stdout(output):
949+
dis.dis(code)
950+
self.assertNotIn('NOP', output.getvalue())
928951

929952
def test_dont_merge_constants(self):
930953
# Issue #25843: compile() must not merge constants which are equal
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix bug where docstring is removed from classes in interactive mode.

Python/codegen.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -746,7 +746,7 @@ _PyCodegen_Expression(compiler *c, expr_ty e)
746746
and for annotations. */
747747

748748
int
749-
_PyCodegen_Body(compiler *c, location loc, asdl_stmt_seq *stmts)
749+
_PyCodegen_Body(compiler *c, location loc, asdl_stmt_seq *stmts, bool is_interactive)
750750
{
751751
/* If from __future__ import annotations is active,
752752
* every annotated class and module should have __annotations__.
@@ -758,7 +758,7 @@ _PyCodegen_Body(compiler *c, location loc, asdl_stmt_seq *stmts)
758758
return SUCCESS;
759759
}
760760
Py_ssize_t first_instr = 0;
761-
if (!IS_INTERACTIVE(c)) {
761+
if (!is_interactive) { /* A string literal on REPL prompt is not a docstring */
762762
PyObject *docstring = _PyAST_GetDocString(stmts);
763763
if (docstring) {
764764
first_instr = 1;
@@ -1432,7 +1432,7 @@ codegen_class_body(compiler *c, stmt_ty s, int firstlineno)
14321432
ADDOP_N_IN_SCOPE(c, loc, STORE_DEREF, &_Py_ID(__classdict__), cellvars);
14331433
}
14341434
/* compile the body proper */
1435-
RETURN_IF_ERROR_IN_SCOPE(c, _PyCodegen_Body(c, loc, s->v.ClassDef.body));
1435+
RETURN_IF_ERROR_IN_SCOPE(c, _PyCodegen_Body(c, loc, s->v.ClassDef.body, false));
14361436
PyObject *static_attributes = _PyCompile_StaticAttributesAsTuple(c);
14371437
if (static_attributes == NULL) {
14381438
_PyCompile_ExitScope(c);

Python/compile.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -790,13 +790,13 @@ compiler_codegen(compiler *c, mod_ty mod)
790790
switch (mod->kind) {
791791
case Module_kind: {
792792
asdl_stmt_seq *stmts = mod->v.Module.body;
793-
RETURN_IF_ERROR(_PyCodegen_Body(c, start_location(stmts), stmts));
793+
RETURN_IF_ERROR(_PyCodegen_Body(c, start_location(stmts), stmts, false));
794794
break;
795795
}
796796
case Interactive_kind: {
797797
c->c_interactive = 1;
798798
asdl_stmt_seq *stmts = mod->v.Interactive.body;
799-
RETURN_IF_ERROR(_PyCodegen_Body(c, start_location(stmts), stmts));
799+
RETURN_IF_ERROR(_PyCodegen_Body(c, start_location(stmts), stmts, true));
800800
break;
801801
}
802802
case Expression_kind: {

0 commit comments

Comments
 (0)