mirror of
https://github.com/python/cpython.git
synced 2024-11-24 17:47:13 +01:00
gh-126072: Set docstring attribute for module and class (#126231)
This commit is contained in:
parent
b19d12f447
commit
6ec886531f
@ -178,6 +178,20 @@ freevars: ()
|
|||||||
nlocals: 3
|
nlocals: 3
|
||||||
flags: 3
|
flags: 3
|
||||||
consts: ("'hello'", "'world'")
|
consts: ("'hello'", "'world'")
|
||||||
|
|
||||||
|
>>> class class_with_docstring:
|
||||||
|
... '''This is a docstring for class'''
|
||||||
|
... '''This line is not docstring'''
|
||||||
|
... pass
|
||||||
|
|
||||||
|
>>> print(class_with_docstring.__doc__)
|
||||||
|
This is a docstring for class
|
||||||
|
|
||||||
|
>>> class class_without_docstring:
|
||||||
|
... pass
|
||||||
|
|
||||||
|
>>> print(class_without_docstring.__doc__)
|
||||||
|
None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
@ -854,6 +868,33 @@ class CodeLocationTest(unittest.TestCase):
|
|||||||
3 * [(42, 42, None, None)],
|
3 * [(42, 42, None, None)],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@cpython_only
|
||||||
|
def test_docstring_under_o2(self):
|
||||||
|
code = textwrap.dedent('''
|
||||||
|
def has_docstring(x, y):
|
||||||
|
"""This is a first-line doc string"""
|
||||||
|
"""This is a second-line doc string"""
|
||||||
|
a = x + y
|
||||||
|
b = x - y
|
||||||
|
return a, b
|
||||||
|
|
||||||
|
|
||||||
|
def no_docstring(x):
|
||||||
|
def g(y):
|
||||||
|
return x + y
|
||||||
|
return g
|
||||||
|
|
||||||
|
|
||||||
|
async def async_func():
|
||||||
|
"""asynf function doc string"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
for func in [has_docstring, no_docstring(4), async_func]:
|
||||||
|
assert(func.__doc__ is None)
|
||||||
|
''')
|
||||||
|
|
||||||
|
rc, out, err = assert_python_ok('-OO', '-c', code)
|
||||||
|
|
||||||
if check_impl_detail(cpython=True) and ctypes is not None:
|
if check_impl_detail(cpython=True) and ctypes is not None:
|
||||||
py = ctypes.pythonapi
|
py = ctypes.pythonapi
|
||||||
|
@ -342,6 +342,10 @@ class TestSpecifics(unittest.TestCase):
|
|||||||
l = lambda: "foo"
|
l = lambda: "foo"
|
||||||
self.assertIsNone(l.__doc__)
|
self.assertIsNone(l.__doc__)
|
||||||
|
|
||||||
|
def test_lambda_consts(self):
|
||||||
|
l = lambda: "this is the only const"
|
||||||
|
self.assertEqual(l.__code__.co_consts, ("this is the only const",))
|
||||||
|
|
||||||
def test_encoding(self):
|
def test_encoding(self):
|
||||||
code = b'# -*- coding: badencoding -*-\npass\n'
|
code = b'# -*- coding: badencoding -*-\npass\n'
|
||||||
self.assertRaises(SyntaxError, compile, code, 'tmp', 'exec')
|
self.assertRaises(SyntaxError, compile, code, 'tmp', 'exec')
|
||||||
@ -790,10 +794,10 @@ class TestSpecifics(unittest.TestCase):
|
|||||||
# Merge constants in tuple or frozenset
|
# Merge constants in tuple or frozenset
|
||||||
f1, f2 = lambda: "not a name", lambda: ("not a name",)
|
f1, f2 = lambda: "not a name", lambda: ("not a name",)
|
||||||
f3 = lambda x: x in {("not a name",)}
|
f3 = lambda x: x in {("not a name",)}
|
||||||
self.assertIs(f1.__code__.co_consts[1],
|
self.assertIs(f1.__code__.co_consts[0],
|
||||||
f2.__code__.co_consts[1][0])
|
f2.__code__.co_consts[0][0])
|
||||||
self.assertIs(next(iter(f3.__code__.co_consts[1])),
|
self.assertIs(next(iter(f3.__code__.co_consts[0])),
|
||||||
f2.__code__.co_consts[1])
|
f2.__code__.co_consts[0])
|
||||||
|
|
||||||
# {0} is converted to a constant frozenset({0}) by the peephole
|
# {0} is converted to a constant frozenset({0}) by the peephole
|
||||||
# optimizer
|
# optimizer
|
||||||
@ -902,6 +906,9 @@ class TestSpecifics(unittest.TestCase):
|
|||||||
|
|
||||||
def with_const_expression():
|
def with_const_expression():
|
||||||
"also" + " not docstring"
|
"also" + " not docstring"
|
||||||
|
|
||||||
|
def multiple_const_strings():
|
||||||
|
"not docstring " * 3
|
||||||
""")
|
""")
|
||||||
|
|
||||||
for opt in [0, 1, 2]:
|
for opt in [0, 1, 2]:
|
||||||
@ -918,6 +925,7 @@ class TestSpecifics(unittest.TestCase):
|
|||||||
self.assertIsNone(ns['two_strings'].__doc__)
|
self.assertIsNone(ns['two_strings'].__doc__)
|
||||||
self.assertIsNone(ns['with_fstring'].__doc__)
|
self.assertIsNone(ns['with_fstring'].__doc__)
|
||||||
self.assertIsNone(ns['with_const_expression'].__doc__)
|
self.assertIsNone(ns['with_const_expression'].__doc__)
|
||||||
|
self.assertIsNone(ns['multiple_const_strings'].__doc__)
|
||||||
|
|
||||||
@support.cpython_only
|
@support.cpython_only
|
||||||
def test_docstring_interactive_mode(self):
|
def test_docstring_interactive_mode(self):
|
||||||
|
@ -820,6 +820,7 @@ Tomáš Hrnčiar
|
|||||||
Miro Hrončok
|
Miro Hrončok
|
||||||
Chiu-Hsiang Hsu
|
Chiu-Hsiang Hsu
|
||||||
Chih-Hao Huang
|
Chih-Hao Huang
|
||||||
|
Xuanteng Huang
|
||||||
Christian Hudon
|
Christian Hudon
|
||||||
Benoît Hudson
|
Benoît Hudson
|
||||||
Lawrence Hudson
|
Lawrence Hudson
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
Following :gh:`126101`, for :ref:`codeobjects` like lambda, annotation and type alias,
|
||||||
|
we no longer add ``None`` to its :attr:`~codeobject.co_consts`.
|
@ -672,9 +672,7 @@ codegen_setup_annotations_scope(compiler *c, location loc,
|
|||||||
codegen_enter_scope(c, name, COMPILE_SCOPE_ANNOTATIONS,
|
codegen_enter_scope(c, name, COMPILE_SCOPE_ANNOTATIONS,
|
||||||
key, loc.lineno, NULL, &umd));
|
key, loc.lineno, NULL, &umd));
|
||||||
|
|
||||||
// Insert None into consts to prevent an annotation
|
assert(!SYMTABLE_ENTRY(c)->ste_has_docstring);
|
||||||
// appearing to be a docstring
|
|
||||||
_PyCompile_AddConst(c, Py_None);
|
|
||||||
// if .format != 1: raise NotImplementedError
|
// if .format != 1: raise NotImplementedError
|
||||||
_Py_DECLARE_STR(format, ".format");
|
_Py_DECLARE_STR(format, ".format");
|
||||||
ADDOP_I(c, loc, LOAD_FAST, 0);
|
ADDOP_I(c, loc, LOAD_FAST, 0);
|
||||||
@ -770,7 +768,8 @@ _PyCodegen_Body(compiler *c, location loc, asdl_stmt_seq *stmts, bool is_interac
|
|||||||
/* If from __future__ import annotations is active,
|
/* If from __future__ import annotations is active,
|
||||||
* every annotated class and module should have __annotations__.
|
* every annotated class and module should have __annotations__.
|
||||||
* Else __annotate__ is created when necessary. */
|
* Else __annotate__ is created when necessary. */
|
||||||
if ((FUTURE_FEATURES(c) & CO_FUTURE_ANNOTATIONS) && SYMTABLE_ENTRY(c)->ste_annotations_used) {
|
PySTEntryObject *ste = SYMTABLE_ENTRY(c);
|
||||||
|
if ((FUTURE_FEATURES(c) & CO_FUTURE_ANNOTATIONS) && ste->ste_annotations_used) {
|
||||||
ADDOP(c, loc, SETUP_ANNOTATIONS);
|
ADDOP(c, loc, SETUP_ANNOTATIONS);
|
||||||
}
|
}
|
||||||
if (!asdl_seq_LEN(stmts)) {
|
if (!asdl_seq_LEN(stmts)) {
|
||||||
@ -778,8 +777,9 @@ _PyCodegen_Body(compiler *c, location loc, asdl_stmt_seq *stmts, bool is_interac
|
|||||||
}
|
}
|
||||||
Py_ssize_t first_instr = 0;
|
Py_ssize_t first_instr = 0;
|
||||||
if (!is_interactive) { /* A string literal on REPL prompt is not a docstring */
|
if (!is_interactive) { /* A string literal on REPL prompt is not a docstring */
|
||||||
PyObject *docstring = _PyAST_GetDocString(stmts);
|
if (ste->ste_has_docstring) {
|
||||||
if (docstring) {
|
PyObject *docstring = _PyAST_GetDocString(stmts);
|
||||||
|
assert(docstring);
|
||||||
first_instr = 1;
|
first_instr = 1;
|
||||||
/* set docstring */
|
/* set docstring */
|
||||||
assert(OPTIMIZATION_LEVEL(c) < 2);
|
assert(OPTIMIZATION_LEVEL(c) < 2);
|
||||||
@ -1241,10 +1241,11 @@ codegen_function_body(compiler *c, stmt_ty s, int is_async, Py_ssize_t funcflags
|
|||||||
RETURN_IF_ERROR(
|
RETURN_IF_ERROR(
|
||||||
codegen_enter_scope(c, name, scope_type, (void *)s, firstlineno, NULL, &umd));
|
codegen_enter_scope(c, name, scope_type, (void *)s, firstlineno, NULL, &umd));
|
||||||
|
|
||||||
|
PySTEntryObject *ste = SYMTABLE_ENTRY(c);
|
||||||
Py_ssize_t first_instr = 0;
|
Py_ssize_t first_instr = 0;
|
||||||
PyObject *docstring = _PyAST_GetDocString(body);
|
if (ste->ste_has_docstring) {
|
||||||
assert(OPTIMIZATION_LEVEL(c) < 2 || docstring == NULL);
|
PyObject *docstring = _PyAST_GetDocString(body);
|
||||||
if (docstring) {
|
assert(docstring);
|
||||||
first_instr = 1;
|
first_instr = 1;
|
||||||
docstring = _PyCompile_CleanDoc(docstring);
|
docstring = _PyCompile_CleanDoc(docstring);
|
||||||
if (docstring == NULL) {
|
if (docstring == NULL) {
|
||||||
@ -1258,7 +1259,6 @@ codegen_function_body(compiler *c, stmt_ty s, int is_async, Py_ssize_t funcflags
|
|||||||
|
|
||||||
NEW_JUMP_TARGET_LABEL(c, start);
|
NEW_JUMP_TARGET_LABEL(c, start);
|
||||||
USE_LABEL(c, start);
|
USE_LABEL(c, start);
|
||||||
PySTEntryObject *ste = SYMTABLE_ENTRY(c);
|
|
||||||
bool add_stopiteration_handler = ste->ste_coroutine || ste->ste_generator;
|
bool add_stopiteration_handler = ste->ste_coroutine || ste->ste_generator;
|
||||||
if (add_stopiteration_handler) {
|
if (add_stopiteration_handler) {
|
||||||
/* codegen_wrap_in_stopiteration_handler will push a block, so we need to account for that */
|
/* 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)
|
|||||||
ADDOP_LOAD_CONST_NEW(c, loc, defaults);
|
ADDOP_LOAD_CONST_NEW(c, loc, defaults);
|
||||||
RETURN_IF_ERROR(
|
RETURN_IF_ERROR(
|
||||||
codegen_setup_annotations_scope(c, LOC(s), s, name));
|
codegen_setup_annotations_scope(c, LOC(s), s, name));
|
||||||
/* Make None the first constant, so the evaluate function can't have a
|
|
||||||
docstring. */
|
assert(!SYMTABLE_ENTRY(c)->ste_has_docstring);
|
||||||
RETURN_IF_ERROR(_PyCompile_AddConst(c, Py_None));
|
|
||||||
VISIT_IN_SCOPE(c, expr, s->v.TypeAlias.value);
|
VISIT_IN_SCOPE(c, expr, s->v.TypeAlias.value);
|
||||||
ADDOP_IN_SCOPE(c, loc, RETURN_VALUE);
|
ADDOP_IN_SCOPE(c, loc, RETURN_VALUE);
|
||||||
PyCodeObject *co = _PyCompile_OptimizeAndAssemble(c, 0);
|
PyCodeObject *co = _PyCompile_OptimizeAndAssemble(c, 0);
|
||||||
@ -1898,9 +1897,7 @@ codegen_lambda(compiler *c, expr_ty e)
|
|||||||
codegen_enter_scope(c, &_Py_STR(anon_lambda), COMPILE_SCOPE_LAMBDA,
|
codegen_enter_scope(c, &_Py_STR(anon_lambda), COMPILE_SCOPE_LAMBDA,
|
||||||
(void *)e, e->lineno, NULL, &umd));
|
(void *)e, e->lineno, NULL, &umd));
|
||||||
|
|
||||||
/* Make None the first constant, so the lambda can't have a
|
assert(!SYMTABLE_ENTRY(c)->ste_has_docstring);
|
||||||
docstring. */
|
|
||||||
RETURN_IF_ERROR(_PyCompile_AddConst(c, Py_None));
|
|
||||||
|
|
||||||
VISIT_IN_SCOPE(c, expr, e->v.Lambda.body);
|
VISIT_IN_SCOPE(c, expr, e->v.Lambda.body);
|
||||||
if (SYMTABLE_ENTRY(c)->ste_generator) {
|
if (SYMTABLE_ENTRY(c)->ste_generator) {
|
||||||
|
@ -434,6 +434,9 @@ _PySymtable_Build(mod_ty mod, PyObject *filename, _PyFutureFeatures *future)
|
|||||||
switch (mod->kind) {
|
switch (mod->kind) {
|
||||||
case Module_kind:
|
case Module_kind:
|
||||||
seq = mod->v.Module.body;
|
seq = mod->v.Module.body;
|
||||||
|
if (_PyAST_GetDocString(seq)) {
|
||||||
|
st->st_cur->ste_has_docstring = 1;
|
||||||
|
}
|
||||||
for (i = 0; i < asdl_seq_LEN(seq); i++)
|
for (i = 0; i < asdl_seq_LEN(seq); i++)
|
||||||
if (!symtable_visit_stmt(st,
|
if (!symtable_visit_stmt(st,
|
||||||
(stmt_ty)asdl_seq_GET(seq, i)))
|
(stmt_ty)asdl_seq_GET(seq, i)))
|
||||||
@ -1909,6 +1912,11 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_PyAST_GetDocString(s->v.ClassDef.body)) {
|
||||||
|
st->st_cur->ste_has_docstring = 1;
|
||||||
|
}
|
||||||
|
|
||||||
VISIT_SEQ(st, stmt, s->v.ClassDef.body);
|
VISIT_SEQ(st, stmt, s->v.ClassDef.body);
|
||||||
if (!symtable_exit_block(st))
|
if (!symtable_exit_block(st))
|
||||||
return 0;
|
return 0;
|
||||||
|
Loading…
Reference in New Issue
Block a user