Skip to content

Commit 8c70e80

Browse files
JukkaLhauntsaninja
andauthored
[mypyc] Support the u8 native integer type (#15564)
This is mostly similar to `i16` that I added recently in #15464, but there are some differences: * Some adjustments were needed to support unsigned integers * Add overflow checking of literals, since it's easy to over/underflow when using `u8` due to limited range * Rename primitive integer types from `int16` to `i16` (etc.) to match the user-visible types (needed to get some error messages consistent, and it's generally nicer) * Overall make things a bit more consistent * Actually update `mypy_extensions` stubs This is an unsigned type to make it easier to work with binary/bytes data. The item values for `bytes` are unsigned 8-bit values, in particular. This type will become much more useful once we support packed arrays. --------- Co-authored-by: Shantanu <[email protected]>
1 parent e0b159e commit 8c70e80

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1843
-634
lines changed

mypy/types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@
155155
"mypy_extensions.i64",
156156
"mypy_extensions.i32",
157157
"mypy_extensions.i16",
158+
"mypy_extensions.u8",
158159
)
159160

160161
DATACLASS_TRANSFORM_NAMES: Final = (

mypy/typeshed/stubs/mypy-extensions/mypy_extensions.pyi

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,3 +181,38 @@ class i16:
181181
def __ge__(self, x: i16) -> bool: ...
182182
def __gt__(self, x: i16) -> bool: ...
183183
def __index__(self) -> int: ...
184+
185+
class u8:
186+
@overload
187+
def __new__(cls, __x: str | ReadableBuffer | SupportsInt | SupportsIndex | SupportsTrunc = ...) -> u8: ...
188+
@overload
189+
def __new__(cls, __x: str | bytes | bytearray, base: SupportsIndex) -> u8: ...
190+
191+
def __add__(self, x: u8) -> u8: ...
192+
def __radd__(self, x: u8) -> u8: ...
193+
def __sub__(self, x: u8) -> u8: ...
194+
def __rsub__(self, x: u8) -> u8: ...
195+
def __mul__(self, x: u8) -> u8: ...
196+
def __rmul__(self, x: u8) -> u8: ...
197+
def __floordiv__(self, x: u8) -> u8: ...
198+
def __rfloordiv__(self, x: u8) -> u8: ...
199+
def __mod__(self, x: u8) -> u8: ...
200+
def __rmod__(self, x: u8) -> u8: ...
201+
def __and__(self, x: u8) -> u8: ...
202+
def __rand__(self, x: u8) -> u8: ...
203+
def __or__(self, x: u8) -> u8: ...
204+
def __ror__(self, x: u8) -> u8: ...
205+
def __xor__(self, x: u8) -> u8: ...
206+
def __rxor__(self, x: u8) -> u8: ...
207+
def __lshift__(self, x: u8) -> u8: ...
208+
def __rlshift__(self, x: u8) -> u8: ...
209+
def __rshift__(self, x: u8) -> u8: ...
210+
def __rrshift__(self, x: u8) -> u8: ...
211+
def __neg__(self) -> u8: ...
212+
def __invert__(self) -> u8: ...
213+
def __pos__(self) -> u8: ...
214+
def __lt__(self, x: u8) -> bool: ...
215+
def __le__(self, x: u8) -> bool: ...
216+
def __ge__(self, x: u8) -> bool: ...
217+
def __gt__(self, x: u8) -> bool: ...
218+
def __index__(self) -> int: ...

mypyc/codegen/emit.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
is_short_int_rprimitive,
4848
is_str_rprimitive,
4949
is_tuple_rprimitive,
50+
is_uint8_rprimitive,
5051
object_rprimitive,
5152
optional_value_type,
5253
)
@@ -922,6 +923,14 @@ def emit_unbox(
922923
self.emit_line(f"{dest} = CPyLong_AsInt16({src});")
923924
if not isinstance(error, AssignHandler):
924925
self.emit_unbox_failure_with_overlapping_error_value(dest, typ, failure)
926+
elif is_uint8_rprimitive(typ):
927+
# Whether we are borrowing or not makes no difference.
928+
assert not optional # Not supported for overlapping error values
929+
if declare_dest:
930+
self.emit_line(f"uint8_t {dest};")
931+
self.emit_line(f"{dest} = CPyLong_AsUInt8({src});")
932+
if not isinstance(error, AssignHandler):
933+
self.emit_unbox_failure_with_overlapping_error_value(dest, typ, failure)
925934
elif is_float_rprimitive(typ):
926935
assert not optional # Not supported for overlapping error values
927936
if declare_dest:
@@ -1013,7 +1022,7 @@ def emit_box(
10131022
self.emit_lines(f"{declaration}{dest} = Py_None;")
10141023
if not can_borrow:
10151024
self.emit_inc_ref(dest, object_rprimitive)
1016-
elif is_int32_rprimitive(typ) or is_int16_rprimitive(typ):
1025+
elif is_int32_rprimitive(typ) or is_int16_rprimitive(typ) or is_uint8_rprimitive(typ):
10171026
self.emit_line(f"{declaration}{dest} = PyLong_FromLong({src});")
10181027
elif is_int64_rprimitive(typ):
10191028
self.emit_line(f"{declaration}{dest} = PyLong_FromLongLong({src});")

mypyc/doc/float_operations.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Construction
1515
* ``float(x: i64)``
1616
* ``float(x: i32)``
1717
* ``float(x: i16)``
18+
* ``float(x: u8)``
1819
* ``float(x: str)``
1920
* ``float(x: float)`` (no-op)
2021

@@ -32,6 +33,7 @@ Functions
3233
* ``i64(f)`` (convert to 64-bit signed integer)
3334
* ``i32(f)`` (convert to 32-bit signed integer)
3435
* ``i16(f)`` (convert to 16-bit signed integer)
36+
* ``u8(f)`` (convert to 8-bit unsigned integer)
3537
* ``abs(f)``
3638
* ``math.sin(f)``
3739
* ``math.cos(f)``

mypyc/doc/int_operations.rst

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@ Mypyc supports these integer types:
99
* ``i64`` (64-bit signed integer)
1010
* ``i32`` (32-bit signed integer)
1111
* ``i16`` (16-bit signed integer)
12+
* ``u8`` (8-bit unsigned integer)
1213

13-
``i64``, ``i32`` and ``i16`` are *native integer types* and must be imported
14-
from the ``mypy_extensions`` module. ``int`` corresponds to the Python
15-
``int`` type, but uses a more efficient runtime representation (tagged
16-
pointer). Native integer types are value types.
14+
``i64``, ``i32``, ``i16`` and ``u8`` are *native integer types* and
15+
are available in the ``mypy_extensions`` module. ``int`` corresponds
16+
to the Python ``int`` type, but uses a more efficient runtime
17+
representation (tagged pointer). Native integer types are value types.
1718

1819
All integer types have optimized primitive operations, but the native
1920
integer types are more efficient than ``int``, since they don't
@@ -34,6 +35,7 @@ Construction
3435
* ``int(x: i64)``
3536
* ``int(x: i32)``
3637
* ``int(x: i16)``
38+
* ``int(x: u8)``
3739
* ``int(x: str)``
3840
* ``int(x: str, base: int)``
3941
* ``int(x: int)`` (no-op)
@@ -42,31 +44,34 @@ Construction
4244

4345
* ``i64(x: int)``
4446
* ``i64(x: float)``
47+
* ``i64(x: i64)`` (no-op)
4548
* ``i64(x: i32)``
4649
* ``i64(x: i16)``
50+
* ``i64(x: u8)``
4751
* ``i64(x: str)``
4852
* ``i64(x: str, base: int)``
49-
* ``i64(x: i64)`` (no-op)
5053

5154
``i32`` type:
5255

5356
* ``i32(x: int)``
5457
* ``i32(x: float)``
5558
* ``i32(x: i64)`` (truncate)
59+
* ``i32(x: i32)`` (no-op)
5660
* ``i32(x: i16)``
61+
* ``i32(x: u8)``
5762
* ``i32(x: str)``
5863
* ``i32(x: str, base: int)``
59-
* ``i32(x: i32)`` (no-op)
6064

6165
``i16`` type:
6266

6367
* ``i16(x: int)``
6468
* ``i16(x: float)``
6569
* ``i16(x: i64)`` (truncate)
6670
* ``i16(x: i32)`` (truncate)
71+
* ``i16(x: i16)`` (no-op)
72+
* ``i16(x: u8)``
6773
* ``i16(x: str)``
6874
* ``i16(x: str, base: int)``
69-
* ``i16(x: i16)`` (no-op)
7075

7176
Conversions from ``int`` to a native integer type raise
7277
``OverflowError`` if the value is too large or small. Conversions from
@@ -80,6 +85,8 @@ Implicit conversions
8085
``int`` values can be implicitly converted to a native integer type,
8186
for convenience. This means that these are equivalent::
8287

88+
from mypy_extensions import i64
89+
8390
def implicit() -> None:
8491
# Implicit conversion of 0 (int) to i64
8592
x: i64 = 0
@@ -107,18 +114,23 @@ Operators
107114
* Comparisons (``==``, ``!=``, ``<``, etc.)
108115
* Augmented assignment (``x += y``, etc.)
109116

110-
If one of the above native integer operations overflows or underflows,
111-
the behavior is undefined. Native integer types should only be used if
112-
all possible values are small enough for the type. For this reason,
113-
the arbitrary-precision ``int`` type is recommended unless the
114-
performance of integer operations is critical.
117+
If one of the above native integer operations overflows or underflows
118+
with signed operands, the behavior is undefined. Signed native integer
119+
types should only be used if all possible values are small enough for
120+
the type. For this reason, the arbitrary-precision ``int`` type is
121+
recommended for signed values unless the performance of integer
122+
operations is critical.
123+
124+
Operations on unsigned integers (``u8``) wrap around on overflow.
115125

116126
It's a compile-time error to mix different native integer types in a
117127
binary operation such as addition. An explicit conversion is required::
118128

119-
def add(x: i64, y: i32) -> None:
120-
a = x + y # Error (i64 + i32)
121-
b = x + i64(y) # OK
129+
from mypy_extensions import i64, i32
130+
131+
def add(x: i64, y: i32) -> None:
132+
a = x + y # Error (i64 + i32)
133+
b = x + i64(y) # OK
122134

123135
You can freely mix a native integer value and an arbitrary-precision
124136
``int`` value in an operation. The native integer type is "sticky"

mypyc/doc/using_type_annotations.rst

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ implementations:
3333
* ``i64`` (:ref:`documentation <native-ints>`, :ref:`native operations <int-ops>`)
3434
* ``i32`` (:ref:`documentation <native-ints>`, :ref:`native operations <int-ops>`)
3535
* ``i16`` (:ref:`documentation <native-ints>`, :ref:`native operations <int-ops>`)
36+
* ``u8`` (:ref:`documentation <native-ints>`, :ref:`native operations <int-ops>`)
3637
* ``float`` (:ref:`native operations <float-ops>`)
3738
* ``bool`` (:ref:`native operations <bool-ops>`)
3839
* ``str`` (:ref:`native operations <str-ops>`)
@@ -344,13 +345,13 @@ Native integer types
344345
--------------------
345346

346347
You can use the native integer types ``i64`` (64-bit signed integer),
347-
``i32`` (32-bit signed integer), and ``i16`` (16-bit signed integer)
348-
if you know that integer values will always fit within fixed
349-
bounds. These types are faster than the arbitrary-precision ``int``
350-
type, since they don't require overflow checks on operations. ``i32``
351-
and ``i16`` may also use less memory than ``int`` values. The types
352-
are imported from the ``mypy_extensions`` module (installed via ``pip
353-
install mypy_extensions``).
348+
``i32`` (32-bit signed integer), ``i16`` (16-bit signed integer), and
349+
``u8`` (8-bit unsigned integer) if you know that integer values will
350+
always fit within fixed bounds. These types are faster than the
351+
arbitrary-precision ``int`` type, since they don't require overflow
352+
checks on operations. They may also use less memory than ``int``
353+
values. The types are imported from the ``mypy_extensions`` module
354+
(installed via ``pip install mypy_extensions``).
354355

355356
Example::
356357

mypyc/ir/ops.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1162,6 +1162,7 @@ class ComparisonOp(RegisterOp):
11621162
}
11631163

11641164
signed_ops: Final = {"==": EQ, "!=": NEQ, "<": SLT, ">": SGT, "<=": SLE, ">=": SGE}
1165+
unsigned_ops: Final = {"==": EQ, "!=": NEQ, "<": ULT, ">": UGT, "<=": ULE, ">=": UGE}
11651166

11661167
def __init__(self, lhs: Value, rhs: Value, op: int, line: int = -1) -> None:
11671168
super().__init__(line)

0 commit comments

Comments
 (0)