Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions mypyc/doc/str_operations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ A subset of these common string formatting expressions are optimized:
Functions
---------

* ``chr(i: int)``
* ``len(s: str)``
* ``ord(s: str)``

Expand Down
1 change: 1 addition & 0 deletions mypyc/lib-rt/CPy.h
Original file line number Diff line number Diff line change
Expand Up @@ -1037,6 +1037,7 @@ PyObject *CPy_Encode(PyObject *obj, PyObject *encoding, PyObject *errors);
Py_ssize_t CPyStr_Count(PyObject *unicode, PyObject *substring, CPyTagged start);
Py_ssize_t CPyStr_CountFull(PyObject *unicode, PyObject *substring, CPyTagged start, CPyTagged end);
CPyTagged CPyStr_Ord(PyObject *obj);
PyObject *CPyStr_Chr(CPyTagged n);
PyObject *CPyStr_Multiply(PyObject *str, CPyTagged count);
PyObject *CPyStr_Lower(PyObject *str);
PyObject *CPyStr_Upper(PyObject *str);
Expand Down
24 changes: 24 additions & 0 deletions mypyc/lib-rt/str_ops.c
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,30 @@ CPyTagged CPyStr_Ord(PyObject *obj) {
return CPY_INT_TAG;
}

PyObject *CPyStr_Chr(CPyTagged n) {
if (likely(CPyTagged_CheckShort(n))) {
Py_ssize_t v = CPyTagged_ShortAsSsize_t(n);
if (likely(v >= 0 && v <= 0x10ffff)) {
return PyUnicode_FromOrdinal((int)v);
}
}
#if CPY_3_13_FEATURES
// Python 3.13+ raises ValueError for all out-of-range values
PyErr_SetString(PyExc_ValueError, "chr() arg not in range(0x110000)");
return NULL;
#else
// Older Python versions convert the argument to a C int first. Note that on
// 32-bit platforms some values that fit in a C int aren't short ints.
Py_ssize_t v = CPyTagged_AsSsize_t(n);
if ((v == -1 && PyErr_Occurred()) || v < INT_MIN || v > INT_MAX) {
PyErr_SetString(PyExc_OverflowError, "Python int too large to convert to C int");
return NULL;
}
// This raises ValueError, since the value is out of range
return PyUnicode_FromOrdinal((int)v);
#endif
}

PyObject *CPyStr_Multiply(PyObject *str, CPyTagged count) {
Py_ssize_t temp_count = CPyTagged_AsSsize_t(count);
if (temp_count == -1 && PyErr_Occurred()) {
Expand Down
9 changes: 9 additions & 0 deletions mypyc/primitives/str_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,15 @@
error_kind=ERR_MAGIC,
)

# chr(int)
function_op(
name="builtins.chr",
arg_types=[int_rprimitive],
return_type=str_rprimitive,
c_function_name="CPyStr_Chr",
error_kind=ERR_MAGIC,
)

# Optimized str indexing for ord(s[i])

# str index adjustment - convert negative index to positive
Expand Down
22 changes: 22 additions & 0 deletions mypyc/test-data/irbuild-str.test
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,28 @@ L0:
r6 = unbox(int, r5)
return r6

[case testChr]
def f(x: int) -> str:
return chr(x)
def g(x: bool) -> str:
return chr(x)
[out]
def f(x):
x :: int
r0 :: str
L0:
r0 = CPyStr_Chr(x)
return r0
def g(x):
x, r0 :: bool
r1 :: int
r2 :: str
L0:
r0 = x << 1
r1 = extend r0: builtins.bool to builtins.int
r2 = CPyStr_Chr(r1)
return r2

[case testOrdOfStrIndex_64bit]
from mypy_extensions import i64, i32, i16, u8
def ord_str_index(s: str, i: int) -> int:
Expand Down
40 changes: 40 additions & 0 deletions mypyc/test-data/run-strings.test
Original file line number Diff line number Diff line change
Expand Up @@ -809,6 +809,10 @@ def test_format_method_python_doc() -> None:

[case testChr]
# Some test cases are from https://docs.python.org/3/howto/unicode.html
import sys
from typing import Any

from testutil import assertRaises

def try_invalid(x: int) -> bool:
try:
Expand Down Expand Up @@ -846,6 +850,42 @@ def test_chr() -> None:
assert try_invalid(-1)
assert try_invalid(1114112)

def test_chr_all_code_points() -> None:
f: Any = chr
for i in range(0x110000):
c = chr(i)
assert c == f(i)
assert ord(c) == i
# Latin-1 characters are cached singletons, like in CPython
assert chr(65 + int()) is chr(65 + int())
assert chr(255 + int()) is chr(255 + int())

def test_chr_bool() -> None:
t = bool(int() + 1)
assert chr(t) == '\x01'
assert chr(not t) == '\x00'

def test_chr_errors() -> None:
f: Any = chr
for n in [-1, 0x110000, 2**31 - 1, 2**31, -2**31, -2**31 - 1, 2**40, -2**40,
2**62 - 1, 2**62, -2**62, -2**62 - 1, 2**70, -2**70]:
try:
f(n)
except Exception as e:
expected_type = type(e)
expected_msg = str(e)
else:
assert False
with assertRaises(expected_type, expected_msg):
chr(n + int())
_, minor, _, _, _ = sys.version_info
if minor >= 13:
with assertRaises(ValueError, "chr() arg not in range(0x110000)"):
chr(2**70 + int())
else:
with assertRaises(OverflowError, "Python int too large to convert to C int"):
chr(2**70 + int())

[case testOrd]
from testutil import assertRaises
from mypy_extensions import i64, i32, i16, u8
Expand Down
Loading