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
the object pointer is stored. If the Python object does not have the required
type, :exc:`TypeError` is raised.
.. _o_ampersand:
``O&`` (object) [*converter*, *anything*]
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
......
This diff is collapsed.
......@@ -1954,6 +1954,8 @@ class Signature:
if not s:
return None
Parameter = cls._parameter_cls
if s.endswith("/)"):
kind = Parameter.POSITIONAL_ONLY
s = s[:-2] + ')'
......@@ -1969,55 +1971,74 @@ class Signature:
if not isinstance(module, ast.Module):
return None
# ast.FunctionDef
f = module.body[0]
parameters = []
empty = Parameter.empty
invalid = object()
def parse_attribute(node):
if not isinstance(node.ctx, ast.Load):
return None
value = node.value
o = parse_node(value)
if o is invalid:
return invalid
if isinstance(value, ast.Name):
name = o
if name not in sys.modules:
return invalid
o = sys.modules[name]
return getattr(o, node.attr, invalid)
def parse_node(node):
if isinstance(node, ast.arg):
if node.annotation != None:
raise ValueError("Annotations are not currently supported")
return node.arg
if isinstance(node, ast.Num):
return node.n
if isinstance(node, ast.Str):
return node.s
if isinstance(node, ast.NameConstant):
return node.value
if isinstance(node, ast.Attribute):
return parse_attribute(node)
if isinstance(node, ast.Name):
module = None
module_dict = {}
module_name = getattr(func, '__module__', None)
if module_name:
module = sys.modules.get(module_name, None)
if module:
module_dict = module.__dict__
sys_module_dict = sys.modules
def parse_name(node):
assert isinstance(node, ast.arg)
if node.annotation != None:
raise ValueError("Annotations are not currently supported")
return node.arg
def wrap_value(s):
try:
value = eval(s, module_dict)
except NameError:
try:
value = eval(s, sys_module_dict)
except NameError:
raise RuntimeError()
if isinstance(value, str):
return ast.Str(value)
if isinstance(value, (int, float)):
return ast.Num(value)
if isinstance(value, bytes):
return ast.Bytes(value)
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):
return invalid
return node.id
return invalid
raise ValueError()
return wrap_value(node.id)
def p(name_node, default_node, default=empty):
name = parse_node(name_node)
name = parse_name(name_node)
if name is invalid:
return None
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:
return None
default = o if o is not invalid else default
......
......@@ -1601,12 +1601,15 @@ class TestSignatureObject(unittest.TestCase):
self.assertTrue(isinstance(signature, inspect.Signature))
def p(name): return signature.parameters[name].default
self.assertEqual(p('s'), 'avocado')
self.assertEqual(p('b'), b'bytes')
self.assertEqual(p('d'), 3.14)
self.assertEqual(p('i'), 35)
self.assertEqual(p('c'), sys.maxsize)
self.assertEqual(p('n'), None)
self.assertEqual(p('t'), True)
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):
with self.assertRaisesRegex(TypeError, 'is not a callable object'):
......
......@@ -88,6 +88,9 @@ Tests
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
requires them. Disable executable bits and shebang lines in test and
benchmark files in order to prevent using a random system python, and in
......
......@@ -618,7 +618,7 @@ curses_window_addch(PyObject *self, PyObject *args)
int group_right_1 = 0;
long attr = 0;
switch (PyTuple_Size(args)) {
switch (PyTuple_GET_SIZE(args)) {
case 1:
if (!PyArg_ParseTuple(args, "O:addch", &ch))
return NULL;
......@@ -650,7 +650,7 @@ curses_window_addch(PyObject *self, PyObject *args)
static PyObject *
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;
int coordinates_group = group_left_1;
......
......@@ -297,7 +297,7 @@ dbm_dbm_get(PyObject *self, PyObject *args)
int group_right_1 = 0;
PyObject *default_value = NULL;
switch (PyTuple_Size(args)) {
switch (PyTuple_GET_SIZE(args)) {
case 1:
if (!PyArg_ParseTuple(args, "s#:get", &key, &key_length))
return NULL;
......@@ -318,7 +318,7 @@ dbm_dbm_get(PyObject *self, PyObject *args)
static PyObject *
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;
......@@ -450,7 +450,7 @@ dbm.open as dbmopen
flags: str="r"
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
(e.g. os.O_RDWR).
......
......@@ -39,7 +39,7 @@ _opcode_stack_effect(PyModuleDef *module, PyObject *args)
int oparg = 0;
int _return_value;
switch (PyTuple_Size(args)) {
switch (PyTuple_GET_SIZE(args)) {
case 1:
if (!PyArg_ParseTuple(args, "i:stack_effect", &opcode))
return NULL;
......@@ -64,7 +64,7 @@ exit:
static int
_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;
if (HAS_ARG(opcode)) {
......
......@@ -2870,7 +2870,7 @@ PyDoc_STRVAR(docstring_with_signature_and_extra_newlines,
);
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"
......@@ -3317,6 +3317,8 @@ PyInit__testcapi(void)
Py_INCREF(&PyInstanceMethod_Type);
PyModule_AddObject(m, "instancemethod", (PyObject *)&PyInstanceMethod_Type);
PyModule_AddIntConstant(m, "the_number_three", 3);
TestError = PyErr_NewException("_testcapi.error", NULL, NULL);
Py_INCREF(TestError);
PyModule_AddObject(m, "error", TestError);
......
......@@ -2401,7 +2401,7 @@ class dir_fd_converter(CConverter):
/*[clinic input]
os.stat -> object(doc_default='stat_result')
os.stat
path : path_t(allow_fd=True)
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)
#define OS_ACCESS_DIR_FD_CONVERTER dir_fd_unavailable
#endif
/*[clinic input]
os.access -> object(doc_default='True if granted, False otherwise')
os.access
path: path_t(allow_fd=True)
Path to be tested; can be string, bytes, or open-file-descriptor int.
......
......@@ -202,7 +202,7 @@ zlib_compress(PyModuleDef *module, PyObject *args)
int group_right_1 = 0;
int level = 0;
switch (PyTuple_Size(args)) {
switch (PyTuple_GET_SIZE(args)) {
case 1:
if (!PyArg_ParseTuple(args, "y*:compress", &bytes))
return NULL;
......@@ -227,7 +227,7 @@ zlib_compress(PyModuleDef *module, PyObject *args)
static PyObject *
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;
Byte *input, *output = NULL;
......
This diff is collapsed.
......@@ -231,20 +231,20 @@ xyz
self._test_clinic("""
verbatim text here
lah dee dah
/*[copy]
/*[copy input]
def
[copy]*/
[copy start generated code]*/
abc
/*[copy checksum: 03cfd743661f07975fa2f1220c5194cbaff48451]*/
/*[copy end generated code: checksum=03cfd743661f07975fa2f1220c5194cbaff48451]*/
xyz
""", """
verbatim text here
lah dee dah
/*[copy]
/*[copy input]
def
[copy]*/
[copy start generated code]*/
def
/*[copy checksum: 7b18d017f89f61cf17d47f92749ea6930a3f1deb]*/
/*[copy end generated code: checksum=7b18d017f89f61cf17d47f92749ea6930a3f1deb]*/
xyz
""")
......@@ -292,17 +292,6 @@ os.access
p = function.parameters['path']
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):
function = self.parse_function("""
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