Skip to content

Commit 74b4bfb

Browse files
committed
[mypyc] Add str.isspace primitive (python#20842)
1 parent 3977343 commit 74b4bfb

6 files changed

Lines changed: 96 additions & 1 deletion

File tree

mypyc/lib-rt/CPy.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -770,7 +770,8 @@ PyObject *CPy_Encode(PyObject *obj, PyObject *encoding, PyObject *errors);
770770
Py_ssize_t CPyStr_Count(PyObject *unicode, PyObject *substring, CPyTagged start);
771771
Py_ssize_t CPyStr_CountFull(PyObject *unicode, PyObject *substring, CPyTagged start, CPyTagged end);
772772
CPyTagged CPyStr_Ord(PyObject *obj);
773-
773+
PyObject *CPyStr_Multiply(PyObject *str, CPyTagged count);
774+
bool CPyStr_IsSpace(PyObject *str);
774775

775776
// Bytes operations
776777

mypyc/lib-rt/str_ops.c

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,3 +621,36 @@ CPyTagged CPyStr_Ord(PyObject *obj) {
621621
PyExc_TypeError, "ord() expected a character, but a string of length %zd found", s);
622622
return CPY_INT_TAG;
623623
}
624+
625+
PyObject *CPyStr_Multiply(PyObject *str, CPyTagged count) {
626+
Py_ssize_t temp_count = CPyTagged_AsSsize_t(count);
627+
if (temp_count == -1 && PyErr_Occurred()) {
628+
PyErr_SetString(PyExc_OverflowError, CPYTHON_LARGE_INT_ERRMSG);
629+
return NULL;
630+
}
631+
return PySequence_Repeat(str, temp_count);
632+
}
633+
634+
635+
bool CPyStr_IsSpace(PyObject *str) {
636+
Py_ssize_t len = PyUnicode_GET_LENGTH(str);
637+
if (len == 0) return false;
638+
639+
if (PyUnicode_IS_ASCII(str)) {
640+
const Py_UCS1 *data = PyUnicode_1BYTE_DATA(str);
641+
for (Py_ssize_t i = 0; i < len; i++) {
642+
if (!_Py_ascii_whitespace[data[i]])
643+
return false;
644+
}
645+
return true;
646+
}
647+
648+
int kind = PyUnicode_KIND(str);
649+
const void *data = PyUnicode_DATA(str);
650+
for (Py_ssize_t i = 0; i < len; i++) {
651+
Py_UCS4 ch = PyUnicode_READ(kind, data, i);
652+
if (!Py_UNICODE_ISSPACE(ch))
653+
return false;
654+
}
655+
return true;
656+
}

mypyc/primitives/str_ops.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,14 @@
375375
error_kind=ERR_NEG_INT,
376376
)
377377

378+
method_op(
379+
name="isspace",
380+
arg_types=[str_rprimitive],
381+
return_type=bool_rprimitive,
382+
c_function_name="CPyStr_IsSpace",
383+
error_kind=ERR_NEVER,
384+
)
385+
378386
# obj.decode()
379387
method_op(
380388
name="decode",

mypyc/test-data/fixtures/ir.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ def removeprefix(self, prefix: str, /) -> str: ...
129129
def removesuffix(self, suffix: str, /) -> str: ...
130130
def islower(self) -> bool: ...
131131
def count(self, substr: str, start: Optional[int] = None, end: Optional[int] = None) -> int: pass
132+
def isspace(self) -> bool: ...
132133

133134
class float:
134135
def __init__(self, x: object) -> None: pass

mypyc/test-data/irbuild-str.test

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -771,3 +771,35 @@ L0:
771771
r0 = 'literal'
772772
r1 = 'literal'
773773
return 1
774+
775+
[case testStrMultiply]
776+
def s_times_i(s: str, n: int) -> str:
777+
return s * n
778+
def i_times_s(s: str, n: int) -> str:
779+
return n * s
780+
[out]
781+
def s_times_i(s, n):
782+
s :: str
783+
n :: int
784+
r0 :: str
785+
L0:
786+
r0 = CPyStr_Multiply(s, n)
787+
return r0
788+
def i_times_s(s, n):
789+
s :: str
790+
n :: int
791+
r0 :: str
792+
L0:
793+
r0 = CPyStr_Multiply(s, n)
794+
return r0
795+
796+
[case testStrIsSpace]
797+
def is_space(x: str) -> bool:
798+
return x.isspace()
799+
[out]
800+
def is_space(x):
801+
x :: str
802+
r0 :: bool
803+
L0:
804+
r0 = CPyStr_IsSpace(x)
805+
return r0

mypyc/test-data/run-strings.test

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1103,3 +1103,23 @@ def test_optional_ne() -> None:
11031103
assert ne_s_opt_s_opt('y', 'x')
11041104
assert ne_s_opt_s_opt(None, 'x')
11051105
assert ne_s_opt_s_opt('x', None)
1106+
1107+
[case testConstantFoldFormatArgs]
1108+
from typing import Final
1109+
1110+
FMT: Final = "{} {}"
1111+
1112+
def test_format() -> None:
1113+
assert FMT.format(400 + 20, "roll" + "up") == "420 rollup"
1114+
1115+
[case testIsSpace]
1116+
from typing import Any
1117+
1118+
def test_isspace() -> None:
1119+
# Verify correctness across all Unicode codepoints.
1120+
# Exercises UCS-1 (0x00-0xFF), UCS-2 (0x100-0xFFFF), and UCS-4 (0x10000-x10FFFF inclusive) string kinds.
1121+
# Any forces generic dispatch so we compare our primitive against stdlib's
1122+
for i in range(0x110000):
1123+
c = chr(i)
1124+
a: Any = c
1125+
assert c.isspace() == a.isspace()

0 commit comments

Comments
 (0)