bpo-37207: Use vectorcall for range() (GH-18464)



This continues the `range()` part of #13930. The complete pull request is stalled on discussions around dicts, but `range()` should not be controversial. (And I plan to open PRs for other parts if this is merged.)
On top of Mark's change, I unified `range_new` and `range_vectorcall`, which had a lot of duplicate code.


https://bugs.python.org/issue37207
diff --git a/Objects/rangeobject.c b/Objects/rangeobject.c
index 343a80c..123ca0b 100644
--- a/Objects/rangeobject.c
+++ b/Objects/rangeobject.c
@@ -2,6 +2,7 @@
 
 #include "Python.h"
 #include "structmember.h"
+#include "pycore_tupleobject.h"
 
 /* Support objects whose length is > PY_SSIZE_T_MAX.
 
@@ -71,34 +72,27 @@
    range(0, 5, -1)
 */
 static PyObject *
-range_new(PyTypeObject *type, PyObject *args, PyObject *kw)
+range_from_array(PyTypeObject *type, PyObject *const *args, Py_ssize_t num_args)
 {
     rangeobject *obj;
     PyObject *start = NULL, *stop = NULL, *step = NULL;
 
-    if (!_PyArg_NoKeywords("range", kw))
-        return NULL;
-
-    Py_ssize_t num_args = PyTuple_GET_SIZE(args);
     switch (num_args) {
         case 3:
-            step = PyTuple_GET_ITEM(args, 2);
+            step = args[2];
             /* fallthrough */
         case 2:
-            start = PyTuple_GET_ITEM(args, 0);
-            start = PyNumber_Index(start);
+            /* Convert borrowed refs to owned refs */
+            start = PyNumber_Index(args[0]);
             if (!start) {
                 return NULL;
             }
-
-            stop = PyTuple_GET_ITEM(args, 1);
-            stop = PyNumber_Index(stop);
+            stop = PyNumber_Index(args[1]);
             if (!stop) {
                 Py_DECREF(start);
                 return NULL;
             }
-
-            step = validate_step(step);
+            step = validate_step(step);  /* Caution, this can clear exceptions */
             if (!step) {
                 Py_DECREF(start);
                 Py_DECREF(stop);
@@ -106,8 +100,7 @@
             }
             break;
         case 1:
-            stop = PyTuple_GET_ITEM(args, 0);
-            stop = PyNumber_Index(stop);
+            stop = PyNumber_Index(args[0]);
             if (!stop) {
                 return NULL;
             }
@@ -126,10 +119,10 @@
                          num_args);
             return NULL;
     }
-
     obj = make_range_object(type, start, stop, step);
-    if (obj != NULL)
+    if (obj != NULL) {
         return (PyObject *) obj;
+    }
 
     /* Failed to create object, release attributes */
     Py_DECREF(start);
@@ -138,6 +131,28 @@
     return NULL;
 }
 
+static PyObject *
+range_new(PyTypeObject *type, PyObject *args, PyObject *kw)
+{
+    if (!_PyArg_NoKeywords("range", kw))
+        return NULL;
+
+    return range_from_array(type, _PyTuple_ITEMS(args), PyTuple_GET_SIZE(args));
+}
+
+
+static PyObject *
+range_vectorcall(PyTypeObject *type, PyObject *const *args,
+                 size_t nargsf, PyObject *kwnames)
+{
+    Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
+    if (kwnames && PyTuple_GET_SIZE(kwnames) != 0) {
+        PyErr_Format(PyExc_TypeError, "range() takes no keyword arguments");
+        return NULL;
+    }
+    return range_from_array(type, args, nargs);
+}
+
 PyDoc_STRVAR(range_doc,
 "range(stop) -> range object\n\
 range(start, stop[, step]) -> range object\n\
@@ -719,6 +734,7 @@
         0,                      /* tp_init */
         0,                      /* tp_alloc */
         range_new,              /* tp_new */
+        .tp_vectorcall = (vectorcallfunc)range_vectorcall
 };
 
 /*********************** range Iterator **************************/