bpo-32677: Add .isascii() to str, bytes and bytearray (GH-5342)

diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c
index dc1515a..692b7be 100644
--- a/Objects/bytearrayobject.c
+++ b/Objects/bytearrayobject.c
@@ -2159,6 +2159,8 @@
      _Py_isalnum__doc__},
     {"isalpha", (PyCFunction)stringlib_isalpha, METH_NOARGS,
      _Py_isalpha__doc__},
+    {"isascii", (PyCFunction)stringlib_isascii, METH_NOARGS,
+     _Py_isascii__doc__},
     {"isdigit", (PyCFunction)stringlib_isdigit, METH_NOARGS,
      _Py_isdigit__doc__},
     {"islower", (PyCFunction)stringlib_islower, METH_NOARGS,
diff --git a/Objects/bytes_methods.c b/Objects/bytes_methods.c
index bd79773..149650f 100644
--- a/Objects/bytes_methods.c
+++ b/Objects/bytes_methods.c
@@ -92,6 +92,26 @@
 }
 
 
+PyDoc_STRVAR_shared(_Py_isascii__doc__,
+"B.isascii() -> bool\n\
+\n\
+Return True if B is empty or all characters in B are ASCII,\n\
+False otherwise.");
+
+PyObject*
+_Py_bytes_isascii(const char *cptr, Py_ssize_t len)
+{
+    const unsigned char *p = (unsigned char *) cptr;
+    const unsigned char *e = p + len;
+    for (; p < e; p++) {
+        if (*p >= 128) {
+            Py_RETURN_FALSE;
+        }
+    }
+    Py_RETURN_TRUE;
+}
+
+
 PyDoc_STRVAR_shared(_Py_isdigit__doc__,
 "B.isdigit() -> bool\n\
 \n\
diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c
index a921d9c..c358756 100644
--- a/Objects/bytesobject.c
+++ b/Objects/bytesobject.c
@@ -2459,6 +2459,8 @@
      _Py_isalnum__doc__},
     {"isalpha", (PyCFunction)stringlib_isalpha, METH_NOARGS,
      _Py_isalpha__doc__},
+    {"isascii", (PyCFunction)stringlib_isascii, METH_NOARGS,
+     _Py_isascii__doc__},
     {"isdigit", (PyCFunction)stringlib_isdigit, METH_NOARGS,
      _Py_isdigit__doc__},
     {"islower", (PyCFunction)stringlib_islower, METH_NOARGS,
diff --git a/Objects/clinic/unicodeobject.c.h b/Objects/clinic/unicodeobject.c.h
index 643ef04..8072516 100644
--- a/Objects/clinic/unicodeobject.c.h
+++ b/Objects/clinic/unicodeobject.c.h
@@ -165,6 +165,27 @@
     return return_value;
 }
 
+PyDoc_STRVAR(unicode_isascii__doc__,
+"isascii($self, /)\n"
+"--\n"
+"\n"
+"Return True if all characters in the string are ASCII, False otherwise.\n"
+"\n"
+"ASCII characters have code points in the range U+0000-U+007F.\n"
+"Empty string is ASCII too.");
+
+#define UNICODE_ISASCII_METHODDEF    \
+    {"isascii", (PyCFunction)unicode_isascii, METH_NOARGS, unicode_isascii__doc__},
+
+static PyObject *
+unicode_isascii_impl(PyObject *self);
+
+static PyObject *
+unicode_isascii(PyObject *self, PyObject *Py_UNUSED(ignored))
+{
+    return unicode_isascii_impl(self);
+}
+
 PyDoc_STRVAR(unicode_islower__doc__,
 "islower($self, /)\n"
 "--\n"
@@ -930,4 +951,4 @@
 {
     return unicode_sizeof_impl(self);
 }
-/*[clinic end generated code: output=1ad4e81b68194264 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=561c88c912b8fe3b input=a9049054013a1b77]*/
diff --git a/Objects/stringlib/ctype.h b/Objects/stringlib/ctype.h
index f054625..fd7b1bd 100644
--- a/Objects/stringlib/ctype.h
+++ b/Objects/stringlib/ctype.h
@@ -23,6 +23,12 @@
 }
 
 static PyObject*
+stringlib_isascii(PyObject *self)
+{
+    return _Py_bytes_isascii(STRINGLIB_STR(self), STRINGLIB_LEN(self));
+}
+
+static PyObject*
 stringlib_isdigit(PyObject *self)
 {
     return _Py_bytes_isdigit(STRINGLIB_STR(self), STRINGLIB_LEN(self));
diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c
index 0733011..4b90cc3 100644
--- a/Objects/unicodeobject.c
+++ b/Objects/unicodeobject.c
@@ -11612,6 +11612,25 @@
 }
 
 /*[clinic input]
+str.isascii as unicode_isascii
+
+Return True if all characters in the string are ASCII, False otherwise.
+
+ASCII characters have code points in the range U+0000-U+007F.
+Empty string is ASCII too.
+[clinic start generated code]*/
+
+static PyObject *
+unicode_isascii_impl(PyObject *self)
+/*[clinic end generated code: output=c5910d64b5a8003f input=5a43cbc6399621d5]*/
+{
+    if (PyUnicode_READY(self) == -1) {
+        return NULL;
+    }
+    return PyBool_FromLong(PyUnicode_IS_ASCII(self));
+}
+
+/*[clinic input]
 str.islower as unicode_islower
 
 Return True if the string is a lowercase string, False otherwise.
@@ -13801,6 +13820,7 @@
     UNICODE_UPPER_METHODDEF
     {"startswith", (PyCFunction) unicode_startswith, METH_VARARGS, startswith__doc__},
     {"endswith", (PyCFunction) unicode_endswith, METH_VARARGS, endswith__doc__},
+    UNICODE_ISASCII_METHODDEF
     UNICODE_ISLOWER_METHODDEF
     UNICODE_ISUPPER_METHODDEF
     UNICODE_ISTITLE_METHODDEF