Skip to content

Commit 6ec8865

Browse files
authored
gh-126072: Set docstring attribute for module and class (#126231)
1 parent b19d12f commit 6ec8865

File tree

6 files changed

+77
-20
lines changed

6 files changed

+77
-20
lines changed

Lib/test/test_code.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,20 @@
178178
nlocals: 3
179179
flags: 3
180180
consts: ("'hello'", "'world'")
181+
182+
>>> class class_with_docstring:
183+
... '''This is a docstring for class'''
184+
... '''This line is not docstring'''
185+
... pass
186+
187+
>>> print(class_with_docstring.__doc__)
188+
This is a docstring for class
189+
190+
>>> class class_without_docstring:
191+
... pass
192+
193+
>>> print(class_without_docstring.__doc__)
194+
None
181195
"""
182196

183197
import copy
@@ -854,6 +868,33 @@ def f():
854868
3 * [(42, 42, None, None)],
855869
)
856870

871+
@cpython_only
872+
def test_docstring_under_o2(self):
873+
code = textwrap.dedent('''
874+
def has_docstring(x, y):
875+
"""This is a first-line doc string"""
876+
"""This is a second-line doc string"""
877+
a = x + y
878+
b = x - y
879+
return a, b
880+
881+
882+
def no_docstring(x):
883+
def g(y):
884+
return x + y
885+
return g
886+
887+
888+
async def async_func():
889+
"""asynf function doc string"""
890+
pass
891+
892+
893+
for func in [has_docstring, no_docstring(4), async_func]:
894+
assert(func.__doc__ is None)
895+
''')
896+
897+
rc, out, err = assert_python_ok('-OO', '-c', code)
857898

858899
if check_impl_detail(cpython=True) and ctypes is not None:
859900
py = ctypes.pythonapi

Lib/test/test_compile.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,10 @@ def test_lambda_doc(self):
342342
l = lambda: "foo"
343343
self.assertIsNone(l.__doc__)
344344

345+
def test_lambda_consts(self):
346+
l = lambda: "this is the only const"
347+
self.assertEqual(l.__code__.co_consts, ("this is the only const",))
348+
345349
def test_encoding(self):
346350
code = b'# -*- coding: badencoding -*-\npass\n'
347351
self.assertRaises(SyntaxError, compile, code, 'tmp', 'exec')
@@ -790,10 +794,10 @@ def check_same_constant(const):
790794
# Merge constants in tuple or frozenset
791795
f1, f2 = lambda: "not a name", lambda: ("not a name",)
792796
f3 = lambda x: x in {("not a name",)}
793-
self.assertIs(f1.__code__.co_consts[1],
794-
f2.__code__.co_consts[1][0])
795-
self.assertIs(next(iter(f3.__code__.co_consts[1])),
796-
f2.__code__.co_consts[1])
797+
self.assertIs(f1.__code__.co_consts[0],
798+
f2.__code__.co_consts[0][0])
799+
self.assertIs(next(iter(f3.__code__.co_consts[0])),
800+
f2.__code__.co_consts[0])
797801

798802
# {0} is converted to a constant frozenset({0}) by the peephole
799803
# optimizer
@@ -902,6 +906,9 @@ def with_fstring():
902906
903907
def with_const_expression():
904908
"also" + " not docstring"
909+
910+
def multiple_const_strings():
911+
"not docstring " * 3
905912
""")
906913

907914
for opt in [0, 1, 2]:
@@ -918,6 +925,7 @@ def with_const_expression():
918925
self.assertIsNone(ns['two_strings'].__doc__)
919926
self.assertIsNone(ns['with_fstring'].__doc__)
920927
self.assertIsNone(ns['with_const_expression'].__doc__)
928+
self.assertIsNone(ns['multiple_const_strings'].__doc__)
921929

922930
@support.cpython_only
923931
def test_docstring_interactive_mode(self):

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -820,6 +820,7 @@ Tomáš Hrnčiar
820820
Miro Hrončok
821821
Chiu-Hsiang Hsu
822822
Chih-Hao Huang
823+
Xuanteng Huang
823824
Christian Hudon
824825
Benoît Hudson
825826
Lawrence Hudson
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Following :gh:`126101`, for :ref:`codeobjects` like lambda, annotation and type alias,
2+
we no longer add ``None`` to its :attr:`~codeobject.co_consts`.

Python/codegen.c

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -672,9 +672,7 @@ codegen_setup_annotations_scope(compiler *c, location loc,
672672
codegen_enter_scope(c, name, COMPILE_SCOPE_ANNOTATIONS,
673673
key, loc.lineno, NULL, &umd));
674674

675-
// Insert None into consts to prevent an annotation
676-
// appearing to be a docstring
677-
_PyCompile_AddConst(c, Py_None);
675+
assert(!SYMTABLE_ENTRY(c)->ste_has_docstring);
678676
// if .format != 1: raise NotImplementedError
679677
_Py_DECLARE_STR(format, ".format");
680678
ADDOP_I(c, loc, LOAD_FAST, 0);
@@ -770,16 +768,18 @@ _PyCodegen_Body(compiler *c, location loc, asdl_stmt_seq *stmts, bool is_interac
770768
/* If from __future__ import annotations is active,
771769
* every annotated class and module should have __annotations__.
772770
* Else __annotate__ is created when necessary. */
773-
if ((FUTURE_FEATURES(c) & CO_FUTURE_ANNOTATIONS) && SYMTABLE_ENTRY(c)->ste_annotations_used) {
771+
PySTEntryObject *ste = SYMTABLE_ENTRY(c);
772+
if ((FUTURE_FEATURES(c) & CO_FUTURE_ANNOTATIONS) && ste->ste_annotations_used) {
774773
ADDOP(c, loc, SETUP_ANNOTATIONS);
775774
}
776775
if (!asdl_seq_LEN(stmts)) {
777776
return SUCCESS;
778777
}
779778
Py_ssize_t first_instr = 0;
780779
if (!is_interactive) { /* A string literal on REPL prompt is not a docstring */
781-
PyObject *docstring = _PyAST_GetDocString(stmts);
782-
if (docstring) {
780+
if (ste->ste_has_docstring) {
781+
PyObject *docstring = _PyAST_GetDocString(stmts);
782+
assert(docstring);
783783
first_instr = 1;
784784
/* set docstring */
785785
assert(OPTIMIZATION_LEVEL(c) < 2);
@@ -1241,10 +1241,11 @@ codegen_function_body(compiler *c, stmt_ty s, int is_async, Py_ssize_t funcflags
12411241
RETURN_IF_ERROR(
12421242
codegen_enter_scope(c, name, scope_type, (void *)s, firstlineno, NULL, &umd));
12431243

1244+
PySTEntryObject *ste = SYMTABLE_ENTRY(c);
12441245
Py_ssize_t first_instr = 0;
1245-
PyObject *docstring = _PyAST_GetDocString(body);
1246-
assert(OPTIMIZATION_LEVEL(c) < 2 || docstring == NULL);
1247-
if (docstring) {
1246+
if (ste->ste_has_docstring) {
1247+
PyObject *docstring = _PyAST_GetDocString(body);
1248+
assert(docstring);
12481249
first_instr = 1;
12491250
docstring = _PyCompile_CleanDoc(docstring);
12501251
if (docstring == NULL) {
@@ -1258,7 +1259,6 @@ codegen_function_body(compiler *c, stmt_ty s, int is_async, Py_ssize_t funcflags
12581259

12591260
NEW_JUMP_TARGET_LABEL(c, start);
12601261
USE_LABEL(c, start);
1261-
PySTEntryObject *ste = SYMTABLE_ENTRY(c);
12621262
bool add_stopiteration_handler = ste->ste_coroutine || ste->ste_generator;
12631263
if (add_stopiteration_handler) {
12641264
/* codegen_wrap_in_stopiteration_handler will push a block, so we need to account for that */
@@ -1600,9 +1600,8 @@ codegen_typealias_body(compiler *c, stmt_ty s)
16001600
ADDOP_LOAD_CONST_NEW(c, loc, defaults);
16011601
RETURN_IF_ERROR(
16021602
codegen_setup_annotations_scope(c, LOC(s), s, name));
1603-
/* Make None the first constant, so the evaluate function can't have a
1604-
docstring. */
1605-
RETURN_IF_ERROR(_PyCompile_AddConst(c, Py_None));
1603+
1604+
assert(!SYMTABLE_ENTRY(c)->ste_has_docstring);
16061605
VISIT_IN_SCOPE(c, expr, s->v.TypeAlias.value);
16071606
ADDOP_IN_SCOPE(c, loc, RETURN_VALUE);
16081607
PyCodeObject *co = _PyCompile_OptimizeAndAssemble(c, 0);
@@ -1898,9 +1897,7 @@ codegen_lambda(compiler *c, expr_ty e)
18981897
codegen_enter_scope(c, &_Py_STR(anon_lambda), COMPILE_SCOPE_LAMBDA,
18991898
(void *)e, e->lineno, NULL, &umd));
19001899

1901-
/* Make None the first constant, so the lambda can't have a
1902-
docstring. */
1903-
RETURN_IF_ERROR(_PyCompile_AddConst(c, Py_None));
1900+
assert(!SYMTABLE_ENTRY(c)->ste_has_docstring);
19041901

19051902
VISIT_IN_SCOPE(c, expr, e->v.Lambda.body);
19061903
if (SYMTABLE_ENTRY(c)->ste_generator) {

Python/symtable.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,9 @@ _PySymtable_Build(mod_ty mod, PyObject *filename, _PyFutureFeatures *future)
434434
switch (mod->kind) {
435435
case Module_kind:
436436
seq = mod->v.Module.body;
437+
if (_PyAST_GetDocString(seq)) {
438+
st->st_cur->ste_has_docstring = 1;
439+
}
437440
for (i = 0; i < asdl_seq_LEN(seq); i++)
438441
if (!symtable_visit_stmt(st,
439442
(stmt_ty)asdl_seq_GET(seq, i)))
@@ -1909,6 +1912,11 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s)
19091912
return 0;
19101913
}
19111914
}
1915+
1916+
if (_PyAST_GetDocString(s->v.ClassDef.body)) {
1917+
st->st_cur->ste_has_docstring = 1;
1918+
}
1919+
19121920
VISIT_SEQ(st, stmt, s->v.ClassDef.body);
19131921
if (!symtable_exit_block(st))
19141922
return 0;

0 commit comments

Comments
 (0)