Commit 2a727916 authored by Larry Hastings's avatar Larry Hastings

Issue #20226: Major improvements to Argument Clinic.

* You may now specify an expression as the default value for a
  parameter!  Example: "sys.maxsize - 1".  This support is
  intentionally quite limited; you may only use values that
  can be represented as static C values.
* Removed "doc_default", simplified support for "c_default"
  and "py_default".  (I'm not sure we still even need
  "py_default", but I'm leaving it in for now in case a
  use presents itself.)
* Parameter lines support a trailing '\\' as a line
  continuation character, allowing you to break up long lines.
* The argument parsing code generated when supporting optional
  groups now uses PyTuple_GET_SIZE instead of PyTuple_GetSize,
  leading to a 850% speedup in parsing.  (Just kidding, this
  is an unmeasurable difference.)
* A bugfix for the recent regression where the generated
  prototype from pydoc for builtins would be littered with
  unreadable "=<object ...>"" default values for parameters
  that had no default value.
* Converted some asserts into proper failure messages.
* Many doc improvements and fixes.
parent e1f55449
...@@ -294,6 +294,8 @@ Other objects ...@@ -294,6 +294,8 @@ Other objects
the object pointer is stored. If the Python object does not have the required the object pointer is stored. If the Python object does not have the required
type, :exc:`TypeError` is raised. type, :exc:`TypeError` is raised.
.. _o_ampersand:
``O&`` (object) [*converter*, *anything*] ``O&`` (object) [*converter*, *anything*]
Convert a Python object to a C variable through a *converter* function. This Convert a Python object to a C variable through a *converter* function. This
takes two arguments: the first is a function, the second is the address of a C takes two arguments: the first is a function, the second is the address of a C
......
This diff is collapsed.
...@@ -1954,6 +1954,8 @@ class Signature: ...@@ -1954,6 +1954,8 @@ class Signature:
if not s: if not s:
return None return None
Parameter = cls._parameter_cls
if s.endswith("/)"): if s.endswith("/)"):
kind = Parameter.POSITIONAL_ONLY kind = Parameter.POSITIONAL_ONLY
s = s[:-2] + ')' s = s[:-2] + ')'
...@@ -1969,55 +1971,74 @@ class Signature: ...@@ -1969,55 +1971,74 @@ class Signature:
if not isinstance(module, ast.Module): if not isinstance(module, ast.Module):
return None return None
# ast.FunctionDef
f = module.body[0] f = module.body[0]
parameters = [] parameters = []
empty = Parameter.empty empty = Parameter.empty
invalid = object() invalid = object()
def parse_attribute(node): module = None
if not isinstance(node.ctx, ast.Load): module_dict = {}
return None module_name = getattr(func, '__module__', None)
if module_name:
value = node.value module = sys.modules.get(module_name, None)
o = parse_node(value) if module:
if o is invalid: module_dict = module.__dict__
return invalid sys_module_dict = sys.modules
if isinstance(value, ast.Name): def parse_name(node):
name = o assert isinstance(node, ast.arg)
if name not in sys.modules: if node.annotation != None:
return invalid raise ValueError("Annotations are not currently supported")
o = sys.modules[name] return node.arg
return getattr(o, node.attr, invalid) def wrap_value(s):
try:
def parse_node(node): value = eval(s, module_dict)
if isinstance(node, ast.arg): except NameError:
if node.annotation != None: try:
raise ValueError("Annotations are not currently supported") value = eval(s, sys_module_dict)
return node.arg except NameError:
if isinstance(node, ast.Num): raise RuntimeError()
return node.n
if isinstance(node, ast.Str): if isinstance(value, str):
return node.s return ast.Str(value)
if isinstance(node, ast.NameConstant): if isinstance(value, (int, float)):
return node.value return ast.Num(value)
if isinstance(node, ast.Attribute): if isinstance(value, bytes):
return parse_attribute(node) return ast.Bytes(value)
if isinstance(node, ast.Name): if value in (True, False, None):
return ast.NameConstant(value)
raise RuntimeError()
class RewriteSymbolics(ast.NodeTransformer):
def visit_Attribute(self, node):
a = []
n = node
while isinstance(n, ast.Attribute):
a.append(n.attr)
n = n.value
if not isinstance(n, ast.Name):
raise RuntimeError()
a.append(n.id)
value = ".".join(reversed(a))
return wrap_value(value)
def visit_Name(self, node):
if not isinstance(node.ctx, ast.Load): if not isinstance(node.ctx, ast.Load):
return invalid raise ValueError()
return node.id return wrap_value(node.id)
return invalid
def p(name_node, default_node, default=empty): def p(name_node, default_node, default=empty):
name = parse_node(name_node) name = parse_name(name_node)
if name is invalid: if name is invalid:
return None return None
if default_node: if default_node:
o = parse_node(default_node) try:
default_node = RewriteSymbolics().visit(default_node)
o = ast.literal_eval(default_node)
except ValueError:
o = invalid
if o is invalid: if o is invalid:
return None return None
default = o if o is not invalid else default default = o if o is not invalid else default
......
...@@ -1601,12 +1601,15 @@ class TestSignatureObject(unittest.TestCase): ...@@ -1601,12 +1601,15 @@ class TestSignatureObject(unittest.TestCase):
self.assertTrue(isinstance(signature, inspect.Signature)) self.assertTrue(isinstance(signature, inspect.Signature))
def p(name): return signature.parameters[name].default def p(name): return signature.parameters[name].default
self.assertEqual(p('s'), 'avocado') self.assertEqual(p('s'), 'avocado')
self.assertEqual(p('b'), b'bytes')
self.assertEqual(p('d'), 3.14) self.assertEqual(p('d'), 3.14)
self.assertEqual(p('i'), 35) self.assertEqual(p('i'), 35)
self.assertEqual(p('c'), sys.maxsize)
self.assertEqual(p('n'), None) self.assertEqual(p('n'), None)
self.assertEqual(p('t'), True) self.assertEqual(p('t'), True)
self.assertEqual(p('f'), False) self.assertEqual(p('f'), False)
self.assertEqual(p('local'), 3)
self.assertEqual(p('sys'), sys.maxsize)
self.assertEqual(p('exp'), sys.maxsize - 1)
def test_signature_on_non_function(self): def test_signature_on_non_function(self):
with self.assertRaisesRegex(TypeError, 'is not a callable object'): with self.assertRaisesRegex(TypeError, 'is not a callable object'):
......
...@@ -88,6 +88,9 @@ Tests ...@@ -88,6 +88,9 @@ Tests
Tools/Demos Tools/Demos
----------- -----------
- Issue #20226: Argument Clinic now permits simple expressions
(e.g. "sys.maxsize - 1") as default values for parameters.
- Issue #19936: Added executable bits or shebang lines to Python scripts which - Issue #19936: Added executable bits or shebang lines to Python scripts which
requires them. Disable executable bits and shebang lines in test and requires them. Disable executable bits and shebang lines in test and
benchmark files in order to prevent using a random system python, and in benchmark files in order to prevent using a random system python, and in
......
...@@ -618,7 +618,7 @@ curses_window_addch(PyObject *self, PyObject *args) ...@@ -618,7 +618,7 @@ curses_window_addch(PyObject *self, PyObject *args)
int group_right_1 = 0; int group_right_1 = 0;
long attr = 0; long attr = 0;
switch (PyTuple_Size(args)) { switch (PyTuple_GET_SIZE(args)) {
case 1: case 1:
if (!PyArg_ParseTuple(args, "O:addch", &ch)) if (!PyArg_ParseTuple(args, "O:addch", &ch))
return NULL; return NULL;
...@@ -650,7 +650,7 @@ curses_window_addch(PyObject *self, PyObject *args) ...@@ -650,7 +650,7 @@ curses_window_addch(PyObject *self, PyObject *args)
static PyObject * static PyObject *
curses_window_addch_impl(PyObject *self, int group_left_1, int x, int y, PyObject *ch, int group_right_1, long attr) curses_window_addch_impl(PyObject *self, int group_left_1, int x, int y, PyObject *ch, int group_right_1, long attr)
/*[clinic end generated code: checksum=44ed958b891cde91205e584c766e048f3999714f]*/ /*[clinic end generated code: checksum=b073327add8197b6ba7fb96c87062422c8312954]*/
{ {
PyCursesWindowObject *cwself = (PyCursesWindowObject *)self; PyCursesWindowObject *cwself = (PyCursesWindowObject *)self;
int coordinates_group = group_left_1; int coordinates_group = group_left_1;
......
...@@ -297,7 +297,7 @@ dbm_dbm_get(PyObject *self, PyObject *args) ...@@ -297,7 +297,7 @@ dbm_dbm_get(PyObject *self, PyObject *args)
int group_right_1 = 0; int group_right_1 = 0;
PyObject *default_value = NULL; PyObject *default_value = NULL;
switch (PyTuple_Size(args)) { switch (PyTuple_GET_SIZE(args)) {
case 1: case 1:
if (!PyArg_ParseTuple(args, "s#:get", &key, &key_length)) if (!PyArg_ParseTuple(args, "s#:get", &key, &key_length))
return NULL; return NULL;
...@@ -318,7 +318,7 @@ dbm_dbm_get(PyObject *self, PyObject *args) ...@@ -318,7 +318,7 @@ dbm_dbm_get(PyObject *self, PyObject *args)
static PyObject * static PyObject *
dbm_dbm_get_impl(dbmobject *dp, const char *key, Py_ssize_clean_t key_length, int group_right_1, PyObject *default_value) dbm_dbm_get_impl(dbmobject *dp, const char *key, Py_ssize_clean_t key_length, int group_right_1, PyObject *default_value)
/*[clinic end generated code: checksum=28cf8928811bde51e535d67ae98ea039d79df717]*/ /*[clinic end generated code: checksum=2c3209571267017f1b9abbd19e1b521849fd5d4a]*/
{ {
datum dbm_key, val; datum dbm_key, val;
...@@ -450,7 +450,7 @@ dbm.open as dbmopen ...@@ -450,7 +450,7 @@ dbm.open as dbmopen
flags: str="r" flags: str="r"
How to open the file. "r" for reading, "w" for writing, etc. How to open the file. "r" for reading, "w" for writing, etc.
mode: int(doc_default="0o666") = 0o666 mode: int(py_default="0o666") = 0o666
If creating a new file, the mode bits for the new file If creating a new file, the mode bits for the new file
(e.g. os.O_RDWR). (e.g. os.O_RDWR).
......
...@@ -39,7 +39,7 @@ _opcode_stack_effect(PyModuleDef *module, PyObject *args) ...@@ -39,7 +39,7 @@ _opcode_stack_effect(PyModuleDef *module, PyObject *args)
int oparg = 0; int oparg = 0;
int _return_value; int _return_value;
switch (PyTuple_Size(args)) { switch (PyTuple_GET_SIZE(args)) {
case 1: case 1:
if (!PyArg_ParseTuple(args, "i:stack_effect", &opcode)) if (!PyArg_ParseTuple(args, "i:stack_effect", &opcode))
return NULL; return NULL;
...@@ -64,7 +64,7 @@ exit: ...@@ -64,7 +64,7 @@ exit:
static int static int
_opcode_stack_effect_impl(PyModuleDef *module, int opcode, int group_right_1, int oparg) _opcode_stack_effect_impl(PyModuleDef *module, int opcode, int group_right_1, int oparg)
/*[clinic end generated code: checksum=e880e62dc7b0de73403026eaf4f8074aa106358b]*/ /*[clinic end generated code: checksum=47e76ec27523da4ab192713642d32482cd743aa4]*/
{ {
int effect; int effect;
if (HAS_ARG(opcode)) { if (HAS_ARG(opcode)) {
......
...@@ -2870,7 +2870,7 @@ PyDoc_STRVAR(docstring_with_signature_and_extra_newlines, ...@@ -2870,7 +2870,7 @@ PyDoc_STRVAR(docstring_with_signature_and_extra_newlines,
); );
PyDoc_STRVAR(docstring_with_signature_with_defaults, PyDoc_STRVAR(docstring_with_signature_with_defaults,
"docstring_with_signature_with_defaults(s='avocado', d=3.14, i=35, c=sys.maxsize, n=None, t=True, f=False)\n" "docstring_with_signature_with_defaults(s='avocado', b=b'bytes', d=3.14, i=35, n=None, t=True, f=False, local=the_number_three, sys=sys.maxsize, exp=sys.maxsize - 1)\n"
"\n" "\n"
"\n" "\n"
"\n" "\n"
...@@ -3317,6 +3317,8 @@ PyInit__testcapi(void) ...@@ -3317,6 +3317,8 @@ PyInit__testcapi(void)
Py_INCREF(&PyInstanceMethod_Type); Py_INCREF(&PyInstanceMethod_Type);
PyModule_AddObject(m, "instancemethod", (PyObject *)&PyInstanceMethod_Type); PyModule_AddObject(m, "instancemethod", (PyObject *)&PyInstanceMethod_Type);
PyModule_AddIntConstant(m, "the_number_three", 3);
TestError = PyErr_NewException("_testcapi.error", NULL, NULL); TestError = PyErr_NewException("_testcapi.error", NULL, NULL);
Py_INCREF(TestError); Py_INCREF(TestError);
PyModule_AddObject(m, "error", TestError); PyModule_AddObject(m, "error", TestError);
......
...@@ -2401,7 +2401,7 @@ class dir_fd_converter(CConverter): ...@@ -2401,7 +2401,7 @@ class dir_fd_converter(CConverter):
/*[clinic input] /*[clinic input]
os.stat -> object(doc_default='stat_result') os.stat
path : path_t(allow_fd=True) path : path_t(allow_fd=True)
Path to be examined; can be string, bytes, or open-file-descriptor int. Path to be examined; can be string, bytes, or open-file-descriptor int.
...@@ -2523,7 +2523,7 @@ posix_lstat(PyObject *self, PyObject *args, PyObject *kwargs) ...@@ -2523,7 +2523,7 @@ posix_lstat(PyObject *self, PyObject *args, PyObject *kwargs)
#define OS_ACCESS_DIR_FD_CONVERTER dir_fd_unavailable #define OS_ACCESS_DIR_FD_CONVERTER dir_fd_unavailable
#endif #endif
/*[clinic input] /*[clinic input]
os.access -> object(doc_default='True if granted, False otherwise') os.access
path: path_t(allow_fd=True) path: path_t(allow_fd=True)
Path to be tested; can be string, bytes, or open-file-descriptor int. Path to be tested; can be string, bytes, or open-file-descriptor int.
......
...@@ -202,7 +202,7 @@ zlib_compress(PyModuleDef *module, PyObject *args) ...@@ -202,7 +202,7 @@ zlib_compress(PyModuleDef *module, PyObject *args)
int group_right_1 = 0; int group_right_1 = 0;
int level = 0; int level = 0;
switch (PyTuple_Size(args)) { switch (PyTuple_GET_SIZE(args)) {
case 1: case 1:
if (!PyArg_ParseTuple(args, "y*:compress", &bytes)) if (!PyArg_ParseTuple(args, "y*:compress", &bytes))
return NULL; return NULL;
...@@ -227,7 +227,7 @@ zlib_compress(PyModuleDef *module, PyObject *args) ...@@ -227,7 +227,7 @@ zlib_compress(PyModuleDef *module, PyObject *args)
static PyObject * static PyObject *
zlib_compress_impl(PyModuleDef *module, Py_buffer *bytes, int group_right_1, int level) zlib_compress_impl(PyModuleDef *module, Py_buffer *bytes, int group_right_1, int level)
/*[clinic end generated code: checksum=2c59af563a4595c5ecea4011701f482ae350aa5f]*/ /*[clinic end generated code: checksum=66c4d16d0b8b9dd423648d9ef00d6a89d3363665]*/
{ {
PyObject *ReturnVal = NULL; PyObject *ReturnVal = NULL;
Byte *input, *output = NULL; Byte *input, *output = NULL;
......
This diff is collapsed.
...@@ -231,20 +231,20 @@ xyz ...@@ -231,20 +231,20 @@ xyz
self._test_clinic(""" self._test_clinic("""
verbatim text here verbatim text here
lah dee dah lah dee dah
/*[copy] /*[copy input]
def def
[copy]*/ [copy start generated code]*/
abc abc
/*[copy checksum: 03cfd743661f07975fa2f1220c5194cbaff48451]*/ /*[copy end generated code: checksum=03cfd743661f07975fa2f1220c5194cbaff48451]*/
xyz xyz
""", """ """, """
verbatim text here verbatim text here
lah dee dah lah dee dah
/*[copy] /*[copy input]
def def
[copy]*/ [copy start generated code]*/
def def
/*[copy checksum: 7b18d017f89f61cf17d47f92749ea6930a3f1deb]*/ /*[copy end generated code: checksum=7b18d017f89f61cf17d47f92749ea6930a3f1deb]*/
xyz xyz
""") """)
...@@ -292,17 +292,6 @@ os.access ...@@ -292,17 +292,6 @@ os.access
p = function.parameters['path'] p = function.parameters['path']
self.assertEqual(1, p.converter.args['allow_fd']) self.assertEqual(1, p.converter.args['allow_fd'])
def test_param_docstring(self):
function = self.parse_function("""
module os
os.stat as os_stat_fn -> object(doc_default='stat_result')
path: str
Path to be examined""")
p = function.parameters['path']
self.assertEqual("Path to be examined", p.docstring)
self.assertEqual(function.return_converter.doc_default, 'stat_result')
def test_function_docstring(self): def test_function_docstring(self):
function = self.parse_function(""" function = self.parse_function("""
module os module os
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment