diff --git a/mypy/types.py b/mypy/types.py index 2c37fd9ee3f8..a50d7fec4ee4 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -203,6 +203,7 @@ class SentinelValue(NamedTuple): "mypy_extensions.i64", "mypy_extensions.i32", "mypy_extensions.i16", + "mypy_extensions.u32", "mypy_extensions.u16", "mypy_extensions.u8", ) diff --git a/mypy/typeshed/stubs/mypy-extensions/mypy_extensions.pyi b/mypy/typeshed/stubs/mypy-extensions/mypy_extensions.pyi index 9b01d2900e5a..a1625eab68e4 100644 --- a/mypy/typeshed/stubs/mypy-extensions/mypy_extensions.pyi +++ b/mypy/typeshed/stubs/mypy-extensions/mypy_extensions.pyi @@ -188,6 +188,43 @@ class i16: def __eq__(self, x: object) -> bool: ... def __bool__(self) -> bool: ... +class u32: + @overload + def __new__(cls, __x: str | ReadableBuffer | SupportsInt | SupportsIndex | SupportsTrunc = ...) -> u32: ... + @overload + def __new__(cls, __x: str | bytes | bytearray, base: SupportsIndex) -> u32: ... + + def __add__(self, x: u32) -> u32: ... + def __radd__(self, x: u32) -> u32: ... + def __sub__(self, x: u32) -> u32: ... + def __rsub__(self, x: u32) -> u32: ... + def __mul__(self, x: u32) -> u32: ... + def __rmul__(self, x: u32) -> u32: ... + def __floordiv__(self, x: u32) -> u32: ... + def __rfloordiv__(self, x: u32) -> u32: ... + def __mod__(self, x: u32) -> u32: ... + def __rmod__(self, x: u32) -> u32: ... + def __and__(self, x: u32) -> u32: ... + def __rand__(self, x: u32) -> u32: ... + def __or__(self, x: u32) -> u32: ... + def __ror__(self, x: u32) -> u32: ... + def __xor__(self, x: u32) -> u32: ... + def __rxor__(self, x: u32) -> u32: ... + def __lshift__(self, x: u32) -> u32: ... + def __rlshift__(self, x: u32) -> u32: ... + def __rshift__(self, x: u32) -> u32: ... + def __rrshift__(self, x: u32) -> u32: ... + def __neg__(self) -> u32: ... + def __invert__(self) -> u32: ... + def __pos__(self) -> u32: ... + def __lt__(self, x: u32) -> bool: ... + def __le__(self, x: u32) -> bool: ... + def __ge__(self, x: u32) -> bool: ... + def __gt__(self, x: u32) -> bool: ... + def __index__(self) -> int: ... + def __eq__(self, x: object) -> bool: ... + def __bool__(self) -> bool: ... + class u16: @overload def __new__(cls, __x: str | ReadableBuffer | SupportsInt | SupportsIndex | SupportsTrunc = ...) -> u16: ... diff --git a/mypyc/codegen/emit.py b/mypyc/codegen/emit.py index c71c3ec04c2f..24bce8cb05fa 100644 --- a/mypyc/codegen/emit.py +++ b/mypyc/codegen/emit.py @@ -66,6 +66,7 @@ is_tuple_rprimitive, is_uint8_rprimitive, is_uint16_rprimitive, + is_uint32_rprimitive, object_rprimitive, optional_value_type, vec_api_by_item_type, @@ -1167,6 +1168,13 @@ def emit_unbox( self.emit_line(f"{dest} = CPyLong_AsInt16({src});") if not isinstance(error, AssignHandler): self.emit_unbox_failure_with_overlapping_error_value(dest, typ, failure) + elif is_uint32_rprimitive(typ): + assert not optional + if declare_dest: + self.emit_line(f"uint32_t {dest};") + self.emit_line(f"{dest} = CPyLong_AsUInt32({src});") + if not isinstance(error, AssignHandler): + self.emit_unbox_failure_with_overlapping_error_value(dest, typ, failure) elif is_uint16_rprimitive(typ): # Whether we are borrowing or not makes no difference. assert not optional # Not supported for overlapping error values @@ -1338,6 +1346,8 @@ def emit_box( self.emit_lines(f"{declaration}{dest} = Py_None;") if not can_borrow: self.emit_inc_ref(dest, object_rprimitive) + elif is_uint32_rprimitive(typ): + self.emit_line(f"{declaration}{dest} = PyLong_FromUnsignedLong({src});") elif ( is_int32_rprimitive(typ) or is_int16_rprimitive(typ) diff --git a/mypyc/doc/int_operations.rst b/mypyc/doc/int_operations.rst index c9243af5e84a..75e1b3af499a 100644 --- a/mypyc/doc/int_operations.rst +++ b/mypyc/doc/int_operations.rst @@ -9,10 +9,11 @@ Mypyc supports these integer types: * ``i64`` (64-bit signed integer) * ``i32`` (32-bit signed integer) * ``i16`` (16-bit signed integer) +* ``u32`` (32-bit unsigned integer) * ``u16`` (16-bit unsigned integer) * ``u8`` (8-bit unsigned integer) -``i64``, ``i32``, ``i16``, ``u16`` and ``u8`` are *native integer types* and +``i64``, ``i32``, ``i16``, ``u32``, ``u16`` and ``u8`` are *native integer types* and are available in the ``mypy_extensions`` module. ``int`` corresponds to the Python ``int`` type, but uses a more efficient runtime representation (tagged pointer). Native integer types are value types. @@ -36,6 +37,7 @@ Construction * ``int(x: i64)`` * ``int(x: i32)`` * ``int(x: i16)`` +* ``int(x: u32)`` * ``int(x: u16)`` * ``int(x: u8)`` * ``int(x: str)`` @@ -49,6 +51,7 @@ Construction * ``i64(x: i64)`` (no-op) * ``i64(x: i32)`` * ``i64(x: i16)`` +* ``i64(x: u32)`` * ``i64(x: u16)`` * ``i64(x: u8)`` * ``i64(x: str)`` @@ -61,6 +64,7 @@ Construction * ``i32(x: i64)`` (truncate) * ``i32(x: i32)`` (no-op) * ``i32(x: i16)`` +* ``i32(x: u32)`` (truncate) * ``i32(x: u16)`` * ``i32(x: u8)`` * ``i32(x: str)`` @@ -73,16 +77,31 @@ Construction * ``i16(x: i64)`` (truncate) * ``i16(x: i32)`` (truncate) * ``i16(x: i16)`` (no-op) +* ``i16(x: u32)`` (truncate) * ``i16(x: u16)`` (truncate) * ``i16(x: u8)`` * ``i16(x: str)`` * ``i16(x: str, base: int)`` +``u32`` type: + +* ``u32(x: int)`` +* ``u32(x: float)`` +* ``u32(x: i64)`` (truncate) +* ``u32(x: i32)`` (truncate) +* ``u32(x: i16)`` (truncate) +* ``u32(x: u32)`` (no-op) +* ``u32(x: u16)`` +* ``u32(x: u8)`` +* ``u32(x: str)`` +* ``u32(x: str, base: int)`` + ``u16`` type: * ``u16(x: int)`` * ``u16(x: float)`` * ``u16(x: i64)`` (truncate) +* ``u16(x: u32)`` (truncate) * ``u16(x: i32)`` (truncate) * ``u16(x: i16)`` (truncate) * ``u16(x: u16)`` (no-op) @@ -150,7 +169,7 @@ the type. For this reason, the arbitrary-precision ``int`` type is recommended for signed values unless the performance of integer operations is critical. -Operations on unsigned integers (``u16`` and ``u8``) wrap around on overflow. +Operations on unsigned integers (``u32``, ``u16`` and ``u8``) wrap around on overflow. It's a compile-time error to mix different native integer types in a binary operation such as addition. An explicit conversion is required:: diff --git a/mypyc/doc/using_type_annotations.rst b/mypyc/doc/using_type_annotations.rst index 4394e9494209..57830da75c2c 100644 --- a/mypyc/doc/using_type_annotations.rst +++ b/mypyc/doc/using_type_annotations.rst @@ -33,6 +33,7 @@ implementations: * ``i64`` (:ref:`documentation `, :ref:`native operations `) * ``i32`` (:ref:`documentation `, :ref:`native operations `) * ``i16`` (:ref:`documentation `, :ref:`native operations `) +* ``u32`` (:ref:`documentation `, :ref:`native operations `) * ``u16`` (:ref:`documentation `, :ref:`native operations `) * ``u8`` (:ref:`documentation `, :ref:`native operations `) * ``float`` (:ref:`native operations `) @@ -350,7 +351,7 @@ Native integer types You can use the native integer types ``i64`` (64-bit signed integer), ``i32`` (32-bit signed integer), ``i16`` (16-bit signed integer), -``u16`` (16-bit unsigned integer), and ``u8`` (8-bit unsigned integer) +``u32`` (32-bit unsigned integer), ``u16`` (16-bit unsigned integer), and ``u8`` (8-bit unsigned integer) if you know that integer values will always fit within fixed bounds. These types are faster than the arbitrary-precision ``int`` type, since they don't require overflow diff --git a/mypyc/ir/rtypes.py b/mypyc/ir/rtypes.py index 5b43a5c30a85..82a5473435b6 100644 --- a/mypyc/ir/rtypes.py +++ b/mypyc/ir/rtypes.py @@ -400,6 +400,16 @@ def __hash__(self) -> int: size=8, error_overlap=True, ) +uint32_rprimitive: Final = RPrimitive( + "u32", + is_unboxed=True, + is_refcounted=False, + is_native_int=True, + is_signed=False, + ctype="uint32_t", + size=4, + error_overlap=True, +) uint16_rprimitive: Final = RPrimitive( "u16", is_unboxed=True, @@ -421,19 +431,9 @@ def __hash__(self) -> int: error_overlap=True, ) -# The following unsigned native int types (u32, u64) are not +# The following unsigned native int types (u64) are not # exposed to the user. They are for internal use within mypyc only. -uint32_rprimitive: Final = RPrimitive( - "u32", - is_unboxed=True, - is_refcounted=False, - is_native_int=True, - is_signed=False, - ctype="uint32_t", - size=4, - error_overlap=True, -) uint64_rprimitive: Final = RPrimitive( "u64", is_unboxed=True, @@ -633,6 +633,7 @@ def is_fixed_width_rtype(rtype: RType) -> TypeGuard[RPrimitive]: is_int64_rprimitive(rtype) or is_int32_rprimitive(rtype) or is_int16_rprimitive(rtype) + or is_uint32_rprimitive(rtype) or is_uint16_rprimitive(rtype) or is_uint8_rprimitive(rtype) ) @@ -767,6 +768,8 @@ def visit_rprimitive(self, t: RPrimitive) -> str: return "4" # "4 byte integer" elif t._ctype == "int16_t": return "2" # "2 byte integer" + elif t._ctype == "uint32_t": + return "U4" elif t._ctype == "uint16_t": return "U2" # "2 byte unsigned integer" elif t._ctype == "uint8_t": diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index c4dde4667228..01ee6dc6442d 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -123,6 +123,7 @@ is_tuple_rprimitive, is_uint8_rprimitive, is_uint16_rprimitive, + is_uint32_rprimitive, none_rprimitive, object_pointer_rprimitive, object_rprimitive, @@ -170,9 +171,12 @@ int64_to_int_op, int_to_int32_op, int_to_int64_op, + int_to_uint32_op, ssize_t_to_int_op, uint8_overflow, uint16_overflow, + uint32_overflow, + uint32_to_int_op, ) from mypyc.primitives.list_ops import list_build_op, list_extend_op, list_items, new_list_op from mypyc.primitives.misc_ops import ( @@ -575,7 +579,7 @@ def coerce_int_to_fixed_width(self, src: Value, target_type: RType, line: int) - assert is_fixed_width_rtype(target_type), target_type assert isinstance(target_type, RPrimitive), target_type - fast, slow, end = BasicBlock(), BasicBlock(), BasicBlock() + fast, slow, overflow, end = BasicBlock(), BasicBlock(), BasicBlock(), BasicBlock() check = self.check_tagged_short_int(src, line) self.add(Branch(check, fast, slow, Branch.BOOL)) @@ -583,6 +587,15 @@ def coerce_int_to_fixed_width(self, src: Value, target_type: RType, line: int) - self.activate_block(fast) size = target_type.size + needs_unsigned_fast_range_check = not target_type.is_signed and size == int_rprimitive.size + if needs_unsigned_fast_range_check: + # A tagged short int always fits in an unsigned type of the same + # width, except for negative values. Don't send those to the slow + # path, which expects a tagged pointer rather than a short int. + nonnegative = self.add(ComparisonOp(src, Integer(0, src.type), ComparisonOp.SGE, line)) + valid = BasicBlock() + self.add(Branch(nonnegative, valid, overflow, Branch.BOOL, line)) + self.activate_block(valid) if size < int_rprimitive.size: # Add a range check when the target type is smaller than the source type # Use helper method to generate range checking and conversion @@ -601,8 +614,10 @@ def coerce_int_to_fixed_width(self, src: Value, target_type: RType, line: int) - self.goto(end) self.activate_block(slow) - if is_int64_rprimitive(target_type) or ( - is_int32_rprimitive(target_type) and size == int_rprimitive.size + if ( + is_int64_rprimitive(target_type) + or (is_int32_rprimitive(target_type) and size == int_rprimitive.size) + or (is_uint32_rprimitive(target_type) and size == int_rprimitive.size) ): # Slow path calls a library function that handles more complex logic ptr = self.int_op( @@ -612,6 +627,8 @@ def coerce_int_to_fixed_width(self, src: Value, target_type: RType, line: int) - self.add(Assign(ptr2, ptr)) if is_int64_rprimitive(target_type): conv_op = int_to_int64_op + elif is_uint32_rprimitive(target_type): + conv_op = int_to_uint32_op else: conv_op = int_to_int32_op tmp = self.call_c(conv_op, [ptr2], line) @@ -622,6 +639,10 @@ def coerce_int_to_fixed_width(self, src: Value, target_type: RType, line: int) - # Slow path just always generates an OverflowError self.emit_fixed_width_overflow_error(target_type, line) + if needs_unsigned_fast_range_check: + self.activate_block(overflow) + self.emit_fixed_width_overflow_error(target_type, line) + self.activate_block(end) return res @@ -723,6 +744,8 @@ def emit_fixed_width_overflow_error(self, target_type: RType, line: int) -> None self.call_c(int32_overflow, [], line) elif is_int16_rprimitive(target_type): self.call_c(int16_overflow, [], line) + elif is_uint32_rprimitive(target_type): + self.call_c(uint32_overflow, [], line) elif is_uint16_rprimitive(target_type): self.call_c(uint16_overflow, [], line) elif is_uint8_rprimitive(target_type): @@ -738,6 +761,19 @@ def coerce_short_int_to_fixed_width(self, src: Value, target_type: RType, line: assert is_fixed_width_rtype(target_type), target_type assert isinstance(target_type, RPrimitive), target_type + if not target_type.is_signed and target_type.size >= short_int_rprimitive.size: + # Any non-negative short int fits in the target type, so only negative + # values need to be rejected. + overflow, valid = BasicBlock(), BasicBlock() + nonnegative = self.add(ComparisonOp(src, Integer(0, src.type), ComparisonOp.SGE, line)) + self.add(Branch(nonnegative, valid, overflow, Branch.BOOL, line)) + self.activate_block(overflow) + self.emit_fixed_width_overflow_error(target_type, line) + self.activate_block(valid) + tmp = src + if target_type.size > short_int_rprimitive.size: + tmp = self.add(Extend(src, target_type, signed=True)) + return self.int_op(target_type, tmp, Integer(1, target_type), IntOp.RIGHT_SHIFT, line) if is_int64_rprimitive(target_type) or ( PLATFORM_SIZE == 4 and is_int32_rprimitive(target_type) ): @@ -762,6 +798,7 @@ def coerce_fixed_width_to_int(self, src: Value, line: int) -> Value: if ( (is_int32_rprimitive(src.type) and PLATFORM_SIZE == 8) or is_int16_rprimitive(src.type) + or (is_uint32_rprimitive(src.type) and PLATFORM_SIZE == 8) or is_uint16_rprimitive(src.type) or is_uint8_rprimitive(src.type) ): @@ -784,12 +821,23 @@ def coerce_fixed_width_to_int(self, src: Value, line: int) -> Value: fast, fast2, slow, end = BasicBlock(), BasicBlock(), BasicBlock(), BasicBlock() - c1 = self.add(ComparisonOp(src, Integer(MAX_SHORT_INT, src_type), ComparisonOp.SLE, line)) - self.add(Branch(c1, fast, slow, Branch.BOOL)) + if src_type.is_signed: + c1 = self.add( + ComparisonOp(src, Integer(MAX_SHORT_INT, src_type), ComparisonOp.SLE, line) + ) + self.add(Branch(c1, fast, slow, Branch.BOOL)) - self.activate_block(fast) - c2 = self.add(ComparisonOp(src, Integer(MIN_SHORT_INT, src_type), ComparisonOp.SGE, line)) - self.add(Branch(c2, fast2, slow, Branch.BOOL)) + self.activate_block(fast) + c2 = self.add( + ComparisonOp(src, Integer(MIN_SHORT_INT, src_type), ComparisonOp.SGE, line) + ) + self.add(Branch(c2, fast2, slow, Branch.BOOL)) + else: + # Unsigned values only need an upper bound check + c1 = self.add( + ComparisonOp(src, Integer(MAX_SHORT_INT, src_type), ComparisonOp.ULE, line) + ) + self.add(Branch(c1, fast2, slow, Branch.BOOL)) self.activate_block(slow) if is_int64_rprimitive(src_type): @@ -797,6 +845,9 @@ def coerce_fixed_width_to_int(self, src: Value, line: int) -> Value: elif is_int32_rprimitive(src_type): assert PLATFORM_SIZE == 4 conv_op = ssize_t_to_int_op + elif is_uint32_rprimitive(src_type): + assert PLATFORM_SIZE == 4 + conv_op = uint32_to_int_op else: assert False, src_type x = self.call_c(conv_op, [src], line) @@ -2731,6 +2782,9 @@ def fixed_width_int_op( prim = int32_divide_op elif is_int16_rprimitive(type): prim = int16_divide_op + elif is_uint32_rprimitive(type): + self.check_for_zero_division(rhs, type, line) + return self.int_op(type, lhs, rhs, op, line) elif is_uint16_rprimitive(type): self.check_for_zero_division(rhs, type, line) return self.int_op(type, lhs, rhs, op, line) @@ -2754,6 +2808,9 @@ def fixed_width_int_op( prim = int32_mod_op elif is_int16_rprimitive(type): prim = int16_mod_op + elif is_uint32_rprimitive(type): + self.check_for_zero_division(rhs, type, line) + return self.int_op(type, lhs, rhs, op, line) elif is_uint16_rprimitive(type): self.check_for_zero_division(rhs, type, line) return self.int_op(type, lhs, rhs, op, line) diff --git a/mypyc/irbuild/mapper.py b/mypyc/irbuild/mapper.py index 670ab80e379d..2e771040a5a1 100644 --- a/mypyc/irbuild/mapper.py +++ b/mypyc/irbuild/mapper.py @@ -51,6 +51,7 @@ tuple_rprimitive, uint8_rprimitive, uint16_rprimitive, + uint32_rprimitive, ) @@ -124,6 +125,8 @@ def type_to_rtype(self, typ: Type | None) -> RType: return int32_rprimitive elif typ.type.fullname == "mypy_extensions.i16": return int16_rprimitive + elif typ.type.fullname == "mypy_extensions.u32": + return uint32_rprimitive elif typ.type.fullname == "mypy_extensions.u16": return uint16_rprimitive elif typ.type.fullname == "mypy_extensions.u8": diff --git a/mypyc/irbuild/specialize.py b/mypyc/irbuild/specialize.py index 1f4cb8bf3f40..c7cc8d495993 100644 --- a/mypyc/irbuild/specialize.py +++ b/mypyc/irbuild/specialize.py @@ -84,6 +84,7 @@ is_tagged, is_uint8_rprimitive, is_uint16_rprimitive, + is_uint32_rprimitive, list_rprimitive, object_rprimitive, set_rprimitive, @@ -91,6 +92,7 @@ string_writer_rprimitive, uint8_rprimitive, uint16_rprimitive, + uint32_rprimitive, ) from mypyc.irbuild.builder import IRBuilder, find_walrus_targets, get_call_target_fullname from mypyc.irbuild.constant_fold import constant_fold_expr @@ -321,6 +323,7 @@ def translate_globals(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Va @specialize_function("mypy_extensions.i64") @specialize_function("mypy_extensions.i32") @specialize_function("mypy_extensions.i16") +@specialize_function("mypy_extensions.u32") @specialize_function("mypy_extensions.u16") @specialize_function("mypy_extensions.u8") def translate_builtins_with_unary_dunder( @@ -334,7 +337,7 @@ def translate_builtins_with_unary_dunder( arg = expr.args[0] arg_typ = builder.node_type(arg) shortname = callee.fullname.split(".")[1] - if shortname in ("i64", "i32", "i16", "u16", "u8"): + if shortname in ("i64", "i32", "i16", "u32", "u16", "u8"): method = "__int__" else: method = f"__{shortname}__" @@ -1178,6 +1181,9 @@ def translate_i64(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value elif is_int32_rprimitive(arg_type) or is_int16_rprimitive(arg_type): val = builder.accept(arg) return builder.add(Extend(val, int64_rprimitive, signed=True, line=expr.line)) + elif is_uint32_rprimitive(arg_type): + val = builder.accept(arg) + return builder.add(Extend(val, int64_rprimitive, signed=False, line=expr.line)) elif is_uint16_rprimitive(arg_type): val = builder.accept(arg) return builder.add(Extend(val, int64_rprimitive, signed=False, line=expr.line)) @@ -1204,6 +1210,9 @@ def translate_i32(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value elif is_int16_rprimitive(arg_type): val = builder.accept(arg) return builder.add(Extend(val, int32_rprimitive, signed=True, line=expr.line)) + elif is_uint32_rprimitive(arg_type): + val = builder.accept(arg) + return builder.add(Truncate(val, int32_rprimitive, line=expr.line)) elif is_uint16_rprimitive(arg_type): val = builder.accept(arg) return builder.add(Extend(val, int32_rprimitive, signed=False, line=expr.line)) @@ -1228,6 +1237,9 @@ def translate_i16(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value elif is_int32_rprimitive(arg_type) or is_int64_rprimitive(arg_type): val = builder.accept(arg) return builder.add(Truncate(val, int16_rprimitive, line=expr.line)) + elif is_uint32_rprimitive(arg_type): + val = builder.accept(arg) + return builder.add(Truncate(val, int16_rprimitive, line=expr.line)) elif is_uint16_rprimitive(arg_type): val = builder.accept(arg) return builder.add(Truncate(val, int16_rprimitive, line=expr.line)) @@ -1241,6 +1253,34 @@ def translate_i16(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value return None +@specialize_function("mypy_extensions.u32") +def translate_u32(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None: + if len(expr.args) != 1 or expr.arg_kinds[0] != ARG_POS: + return None + arg = expr.args[0] + arg_type = builder.node_type(arg) + if is_uint32_rprimitive(arg_type): + return builder.accept(arg) + elif ( + is_int16_rprimitive(arg_type) + or is_int32_rprimitive(arg_type) + or is_int64_rprimitive(arg_type) + or is_uint16_rprimitive(arg_type) + or is_uint8_rprimitive(arg_type) + ): + val = builder.accept(arg) + if is_uint16_rprimitive(arg_type) or is_uint8_rprimitive(arg_type): + return builder.add(Extend(val, uint32_rprimitive, signed=False, line=expr.line)) + if is_int16_rprimitive(arg_type): + return builder.add(Extend(val, uint32_rprimitive, signed=True, line=expr.line)) + return builder.add(Truncate(val, uint32_rprimitive, line=expr.line)) + elif is_int_rprimitive(arg_type) or is_bool_rprimitive(arg_type): + val = builder.accept(arg) + val = truncate_literal(val, uint32_rprimitive) + return builder.coerce(val, uint32_rprimitive, expr.line) + return None + + @specialize_function("mypy_extensions.u16") def translate_u16(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None: if len(expr.args) != 1 or expr.arg_kinds[0] != ARG_POS: @@ -1253,6 +1293,7 @@ def translate_u16(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value is_int16_rprimitive(arg_type) or is_int32_rprimitive(arg_type) or is_int64_rprimitive(arg_type) + or is_uint32_rprimitive(arg_type) or is_uint8_rprimitive(arg_type) ): val = builder.accept(arg) @@ -1278,6 +1319,7 @@ def translate_u8(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | is_int16_rprimitive(arg_type) or is_int32_rprimitive(arg_type) or is_int64_rprimitive(arg_type) + or is_uint32_rprimitive(arg_type) or is_uint16_rprimitive(arg_type) ): val = builder.accept(arg) diff --git a/mypyc/lib-rt/CPy.h b/mypyc/lib-rt/CPy.h index 5aecc178c7bd..65cc8f038e50 100644 --- a/mypyc/lib-rt/CPy.h +++ b/mypyc/lib-rt/CPy.h @@ -175,6 +175,7 @@ static inline size_t CPy_FindAttrOffset(PyTypeObject *trait, CPyVTableItem *vtab CPyTagged CPyTagged_FromSsize_t(Py_ssize_t value); CPyTagged CPyTagged_FromVoidPtr(void *ptr); CPyTagged CPyTagged_FromInt64(int64_t value); +CPyTagged CPyTagged_FromUInt32(uint32_t value); PyObject *CPyTagged_AsObject(CPyTagged x); PyObject *CPyTagged_StealAsObject(CPyTagged x); Py_ssize_t CPyTagged_AsSsize_t(CPyTagged x); @@ -218,6 +219,8 @@ int16_t CPyLong_AsInt16_(PyObject *o); int16_t CPyInt16_Divide(int16_t x, int16_t y); int16_t CPyInt16_Remainder(int16_t x, int16_t y); void CPyInt16_Overflow(void); +uint32_t CPyLong_AsUInt32_(PyObject *o); +void CPyUInt32_Overflow(void); uint16_t CPyLong_AsUInt16_(PyObject *o); void CPyUInt16_Overflow(void); uint8_t CPyLong_AsUInt8_(PyObject *o); @@ -459,6 +462,32 @@ static inline int16_t CPyLong_AsInt16(PyObject *o) { return CPyLong_AsInt16_(o); } +static inline uint32_t CPyLong_AsUInt32(PyObject *o) { + if (likely(PyLong_Check(o))) { + #if CPY_3_12_FEATURES + PyLongObject *lobj = (PyLongObject *)o; + size_t tag = CPY_LONG_TAG(lobj); + if (likely(tag == (1 << CPY_NON_SIZE_BITS))) { + // Fast path + return CPY_LONG_DIGIT(lobj, 0); + } else if (likely(tag == CPY_SIGN_ZERO)) { + return 0; + } + #else + PyLongObject *lobj = (PyLongObject *)o; + Py_ssize_t size = lobj->ob_base.ob_size; + if (likely(size == 1)) { + // Fast path + return CPY_LONG_DIGIT(lobj, 0); + } else if (likely(size == 0)) { + return 0; + } + #endif + } + // Slow path + return CPyLong_AsUInt32_(o); +} + static inline uint16_t CPyLong_AsUInt16(PyObject *o) { if (likely(PyLong_Check(o))) { #if CPY_3_12_FEATURES diff --git a/mypyc/lib-rt/int_ops.c b/mypyc/lib-rt/int_ops.c index 94b09fea192f..edf2fc2f1b96 100644 --- a/mypyc/lib-rt/int_ops.c +++ b/mypyc/lib-rt/int_ops.c @@ -59,6 +59,18 @@ CPyTagged CPyTagged_FromInt64(int64_t value) { } } +CPyTagged CPyTagged_FromUInt32(uint32_t value) { +#if SIZEOF_SIZE_T == 4 + // Only 32-bit platforms can have values that don't fit in a short int. Doing + // the comparison on 64-bit platforms would trigger a tautological compare warning. + if (unlikely(value > CPY_TAGGED_MAX)) { + PyObject *object = PyLong_FromUnsignedLong(value); + return ((CPyTagged)object) | CPY_INT_TAG; + } +#endif + return ((CPyTagged)value) << 1; +} + PyObject *CPyTagged_AsObject(CPyTagged x) { PyObject *value; if (unlikely(CPyTagged_CheckLong(x))) { @@ -569,6 +581,27 @@ void CPyInt16_Overflow() { PyErr_SetString(PyExc_ValueError, "int too large to convert to i16"); } +// u32 unboxing slow path +uint32_t CPyLong_AsUInt32_(PyObject *o) { + unsigned long result = PyLong_AsUnsignedLong(o); + if (result == (unsigned long)-1 && PyErr_Occurred()) { + if (PyErr_ExceptionMatches(PyExc_OverflowError)) { + PyErr_Clear(); + PyErr_SetString(PyExc_ValueError, "int too large or small to convert to u32"); + } + return CPY_LL_UINT_ERROR; + } + if (result > UINT32_MAX) { + PyErr_SetString(PyExc_ValueError, "int too large or small to convert to u32"); + return CPY_LL_UINT_ERROR; + } + return (uint32_t)result; +} + +void CPyUInt32_Overflow() { + PyErr_SetString(PyExc_ValueError, "int too large or small to convert to u32"); +} + // u16 unboxing slow path uint16_t CPyLong_AsUInt16_(PyObject *o) { int overflow; diff --git a/mypyc/primitives/int_ops.py b/mypyc/primitives/int_ops.py index c8734765e340..cd2049163d9a 100644 --- a/mypyc/primitives/int_ops.py +++ b/mypyc/primitives/int_ops.py @@ -30,6 +30,7 @@ int_rprimitive, object_rprimitive, str_rprimitive, + uint32_rprimitive, void_rtype, ) from mypyc.primitives.registry import ( @@ -49,6 +50,7 @@ "mypy_extensions.i64", "mypy_extensions.i32", "mypy_extensions.i16", + "mypy_extensions.u32", "mypy_extensions.u16", "mypy_extensions.u8", ): @@ -294,6 +296,21 @@ def int_unary_op(name: str, c_function_name: str) -> PrimitiveDescription: error_kind=ERR_MAGIC, ) +uint32_to_int_op = custom_op( + arg_types=[uint32_rprimitive], + return_type=int_rprimitive, + c_function_name="CPyTagged_FromUInt32", + error_kind=ERR_MAGIC, +) + +# Convert tagged int (as PyObject *) to u32 +int_to_uint32_op = custom_op( + arg_types=[object_rprimitive], + return_type=uint32_rprimitive, + c_function_name="CPyLong_AsUInt32", + error_kind=ERR_MAGIC_OVERLAPPING, +) + # Convert tagged int (as PyObject *) to i32 int_to_int32_op = custom_op( arg_types=[object_rprimitive], @@ -316,6 +333,13 @@ def int_unary_op(name: str, c_function_name: str) -> PrimitiveDescription: error_kind=ERR_ALWAYS, ) +uint32_overflow = custom_op( + arg_types=[], + return_type=void_rtype, + c_function_name="CPyUInt32_Overflow", + error_kind=ERR_ALWAYS, +) + uint16_overflow = custom_op( arg_types=[], return_type=void_rtype, diff --git a/mypyc/test-data/irbuild-u32.test b/mypyc/test-data/irbuild-u32.test new file mode 100644 index 000000000000..6e347722e735 --- /dev/null +++ b/mypyc/test-data/irbuild-u32.test @@ -0,0 +1,342 @@ +# Test cases for u32 native ints. Focus on things that are different from u8; no need to +# duplicate all u8 test cases here. + +[case testU32BinaryOp] +from mypy_extensions import u32 + +def add_op(x: u32, y: u32) -> u32: + x = y + x + y = x + 5 + y += x + y += 7 + x = 5 + y + return x +def compare(x: u32, y: u32) -> None: + a = x == y + b = x == 5 + c = x < y + d = x < 5 + e = 5 == x + f = 5 < x +[out] +def add_op(x, y): + x, y, r0, r1, r2, r3, r4 :: u32 +L0: + r0 = y + x + x = r0 + r1 = x + 5 + y = r1 + r2 = y + x + y = r2 + r3 = y + 7 + y = r3 + r4 = 5 + y + x = r4 + return x +def compare(x, y): + x, y :: u32 + r0 :: bit + a :: bool + r1 :: bit + b :: bool + r2 :: bit + c :: bool + r3 :: bit + d :: bool + r4 :: bit + e :: bool + r5 :: bit + f :: bool +L0: + r0 = x == y + a = r0 + r1 = x == 5 + b = r1 + r2 = x < y :: unsigned + c = r2 + r3 = x < 5 :: unsigned + d = r3 + r4 = 5 == x + e = r4 + r5 = 5 < x :: unsigned + f = r5 + return 1 + +[case testU32DetectMoreOutOfRangeLiterals_64bit] +from mypy_extensions import u32 + +def out_of_range() -> None: + a: u32 = 4294967296 + b: u32 = -1 + f(4294967296) + # The following are ok + c: u32 = 0 + d: u32 = 4294967295 + f(0) + f(4294967295) + +def f(x: u32) -> None: pass +[out] +main:4: error: Value 4294967296 is out of range for "u32" +main:5: error: Value -1 is out of range for "u32" +main:6: error: Value 4294967296 is out of range for "u32" + +[case testU32BoxAndUnbox] +from typing import Any +from mypy_extensions import u32 + +def f(x: Any) -> Any: + y: u32 = x + return y +[out] +def f(x): + x :: object + r0, y :: u32 + r1 :: object +L0: + r0 = unbox(u32, x) + y = r0 + r1 = box(u32, y) + return r1 + +[case testU32ConvertToInt_64bit] +from mypy_extensions import u32 + +def u32_to_int(a: u32) -> int: + return a +[out] +def u32_to_int(a): + a :: u32 + r0 :: native_int + r1 :: int +L0: + r0 = extend a: u32 to native_int + r1 = r0 << 1 + return r1 + +[case testU32ExplicitConversionFromNativeInt] +from mypy_extensions import i64, i32, i16, u8, u32 + +def from_u32(x: u32) -> u32: + return u32(x) + +def from_u8(x: u8) -> u32: + return u32(x) + +def from_i16(x: i16) -> u32: + return u32(x) + +def from_i32(x: i32) -> u32: + return u32(x) + +def from_i64(x: i64) -> u32: + return u32(x) +[out] +def from_u32(x): + x :: u32 +L0: + return x +def from_u8(x): + x :: u8 + r0 :: u32 +L0: + r0 = extend x: u8 to u32 + return r0 +def from_i16(x): + x :: i16 + r0 :: u32 +L0: + r0 = extend signed x: i16 to u32 + return r0 +def from_i32(x): + x :: i32 + r0 :: u32 +L0: + r0 = truncate x: i32 to u32 + return r0 +def from_i64(x): + x :: i64 + r0 :: u32 +L0: + r0 = truncate x: i64 to u32 + return r0 + +[case testU32ExplicitConversionToNativeInt] +from mypy_extensions import i64, i32, i16, u8, u32 + +def to_u8(x: u32) -> u8: + return u8(x) + +def to_i16(x: u32) -> i16: + return i16(x) + +def to_i32(x: u32) -> i32: + return i32(x) + +def to_i64(x: u32) -> i64: + return i64(x) +[out] +def to_u8(x): + x :: u32 + r0 :: u8 +L0: + r0 = truncate x: u32 to u8 + return r0 +def to_i16(x): + x :: u32 + r0 :: i16 +L0: + r0 = truncate x: u32 to i16 + return r0 +def to_i32(x): + x :: u32 + r0 :: i32 +L0: + r0 = truncate x: u32 to i32 + return r0 +def to_i64(x): + x :: u32 + r0 :: i64 +L0: + r0 = extend x: u32 to i64 + return r0 + +[case testU32ExplicitConversionFromInt_64bit] +from mypy_extensions import u32 + +def f(x: int) -> u32: + return u32(x) +[out] +def f(x): + x :: int + r0 :: native_int + r1, r2, r3 :: bit + r4 :: native_int + r5, r6 :: u32 +L0: + r0 = x & 1 + r1 = r0 == 0 + if r1 goto L1 else goto L4 :: bool +L1: + r2 = x < 8589934592 :: signed + if r2 goto L2 else goto L4 :: bool +L2: + r3 = x >= 0 :: signed + if r3 goto L3 else goto L4 :: bool +L3: + r4 = x >> 1 + r5 = truncate r4: native_int to u32 + r6 = r5 + goto L5 +L4: + CPyUInt32_Overflow() + unreachable +L5: + return r6 + +[case testU32ExplicitConversionFromLiteral_64bit] +from mypy_extensions import u32 + +def f() -> None: + x = u32(0) + y = u32(11) + z = u32(-3) # Truncate + zz = u32(4294967298) # Truncate + a = u32(4294967295) +[out] +def f(): + x, y, z, zz, a :: u32 +L0: + x = 0 + y = 11 + z = 4294967293 + zz = 2 + a = 4294967295 + return 1 + +[case testU32FromShortInt_32bit] +from mypy_extensions import u32 + +def f(a: list[int]) -> u32: + x: u32 = len(a) + return x +[out] +def f(a): + a :: list + r0 :: native_int + r1 :: short_int + r2 :: bit + r3, x :: u32 +L0: + r0 = var_object_size a + r1 = r0 << 1 + r2 = r1 >= 0 :: signed + if r2 goto L2 else goto L1 :: bool +L1: + CPyUInt32_Overflow() + unreachable +L2: + r3 = r1 >> 1 + x = r3 + return x + +[case testU32ConvertToInt_32bit] +from mypy_extensions import u32 + +def u32_to_int(a: u32) -> int: + return a +[out] +def u32_to_int(a): + a :: u32 + r0 :: bit + r1, r2, r3 :: int +L0: + r0 = a <= 1073741823 :: unsigned + if r0 goto L2 else goto L1 :: bool +L1: + r1 = CPyTagged_FromUInt32(a) + r2 = r1 + goto L3 +L2: + r3 = a << 1 + r2 = r3 +L3: + return r2 + +[case testU32ExplicitConversionFromInt_32bit] +from mypy_extensions import u32 + +def f(x: int) -> u32: + return u32(x) +[out] +def f(x): + x :: int + r0 :: native_int + r1, r2 :: bit + r3, r4 :: u32 + r5 :: ptr + r6 :: c_ptr + r7 :: u32 +L0: + r0 = x & 1 + r1 = r0 == 0 + if r1 goto L1 else goto L3 :: bool +L1: + r2 = x >= 0 :: signed + if r2 goto L2 else goto L4 :: bool +L2: + r3 = x >> 1 + r4 = r3 + goto L5 +L3: + r5 = x ^ 1 + r6 = r5 + r7 = CPyLong_AsUInt32(r6) + r4 = r7 + keep_alive x + goto L5 +L4: + CPyUInt32_Overflow() + unreachable +L5: + return r4 diff --git a/mypyc/test-data/run-u32.test b/mypyc/test-data/run-u32.test new file mode 100644 index 000000000000..8f7b727aeb12 --- /dev/null +++ b/mypyc/test-data/run-u32.test @@ -0,0 +1,350 @@ +[case testU32BasicOps] +from typing import Any, Final, Tuple + +from mypy_extensions import u32, i16, i32, i64, u8 + +from testutil import assertRaises + +ERROR: Final = 239 + +def test_box_and_unbox() -> None: + for i in [0, 1, 255, 256, 1000, 32767, 65534, 4294967295]: + o: Any = i + x: u32 = o + o2: Any = x + assert o == o2 + assert x == i + with assertRaises(ValueError, "int too large or small to convert to u32"): + o = 4294967296 + x2: u32 = o + with assertRaises(ValueError, "int too large or small to convert to u32"): + o = -1 + x3: u32 = o + +def div_by_7(x: u32) -> u32: + return x // 7 + +def div(x: u32, y: u32) -> u32: + return x // y + +def test_divide_by_constant() -> None: + for i in [0, 1, 100, 1000, 4294967295]: + assert div_by_7(i) == i // 7 + +def test_divide_by_variable() -> None: + for x in [0, 1, 255, 256, 1000, 4294967295]: + for y in [0, 1, 7, 255, 256, 1000, 4294967295]: + if y != 0: + assert div(x, y) == x // y + else: + with assertRaises(ZeroDivisionError, "integer division or modulo by zero"): + div(x, y) + +def mod_by_7(x: u32) -> u32: + return x % 7 + +def mod(x: u32, y: u32) -> u32: + return x % y + +def test_mod_by_constant() -> None: + for i in [0, 1, 100, 1000, 4294967295]: + assert mod_by_7(i) == i % 7 + +def test_mod_by_variable() -> None: + for x in [0, 1, 255, 256, 1000, 4294967295]: + for y in [0, 1, 7, 255, 256, 1000, 4294967295]: + if y != 0: + assert mod(x, y) == x % y + else: + with assertRaises(ZeroDivisionError, "integer division or modulo by zero"): + mod(x, y) + +def test_simple_arithmetic_ops() -> None: + zero: u32 = int() + one: u32 = zero + 1 + two: u32 = one + 1 + neg_one: u32 = -one + assert neg_one == 4294967295 + assert one + one == 2 + assert one + two == 3 + assert one + neg_one == 0 + assert one - one == 0 + assert one - two == 4294967295 + assert one * one == 1 + assert one * two == 2 + assert two * two == 4 + assert two * neg_one == 4294967294 + assert neg_one * one == 4294967295 + assert neg_one * neg_one == 1 + assert two * 0 == 0 + assert 0 * two == 0 + assert -one == 4294967295 + assert -two == 4294967294 + assert -neg_one == 1 + assert -zero == 0 + +def test_bitwise_ops() -> None: + x: u32 = 47384 + int() + y: u32 = 20239 + int() + z: u32 = 28913 + int() + zero: u32 = int() + one: u32 = zero + 1 + two: u32 = zero + 2 + neg_one: u32 = -one + + assert x & y == 2312 + assert x & z == 12304 + assert z & z == z + assert x & zero == 0 + + assert x | y == 65311 + assert x | z == 63993 + assert z | z == z + assert x | 0 == x + + assert x ^ y == 62999 + assert x ^ z == 51689 + assert z ^ z == 0 + assert z ^ 0 == z + + assert x << one == 94768 + assert x << two == 189536 + assert z << two == 115652 + assert z << 0 == z + + assert x >> one == 23692 + assert x >> two == 11846 + assert z >> two == 7228 + assert z >> 0 == z + + for i in [0, 1, 255, 256, 1000, 32767, 4294967295]: + t: u32 = i + assert ~t == (~(i + int()) & 0xffffffff) + +def eq(x: u32, y: u32) -> bool: + return x == y + +def test_eq() -> None: + assert eq(int(), int()) + assert eq(5 + int(), 5 + int()) + assert not eq(int(), 1 + int()) + assert not eq(5 + int(), 6 + int()) + +def test_comparisons() -> None: + one: u32 = 1 + int() + one2: u32 = 1 + int() + two: u32 = 2 + int() + assert one < two + assert not (one < one2) + assert not (two < one) + assert two > one + assert not (one > one2) + assert not (one > two) + assert one <= two + assert one <= one2 + assert not (two <= one) + assert two >= one + assert one >= one2 + assert not (one >= two) + assert one == one2 + assert not (one == two) + assert one != two + assert not (one != one2) + +def test_mixed_comparisons() -> None: + u32_3: u32 = int() + 3 + int_5 = int() + 5 + assert u32_3 < int_5 + assert int_5 > u32_3 + b = u32_3 > int_5 + assert not b + + int_largest = int() + 4294967295 + assert int_largest > u32_3 + int_smallest = int() + assert u32_3 > int_smallest + + int_too_big = int() + 4294967296 + int_too_small = int() -1 + with assertRaises(ValueError): + assert u32_3 < int_too_big + with assertRaises(ValueError): + assert int_too_big < u32_3 + with assertRaises(ValueError): + assert u32_3 > int_too_small + with assertRaises(ValueError): + assert int_too_small < u32_3 + +def test_mixed_arithmetic_and_bitwise_ops() -> None: + u32_3: u32 = int() + 3 + int_5 = int() + 5 + assert u32_3 + int_5 == 8 + assert int_5 - u32_3 == 2 + assert u32_3 << int_5 == 96 + assert int_5 << u32_3 == 40 + assert u32_3 ^ int_5 == 6 + assert int_5 | u32_3 == 7 + + int_largest = int() + 4294967295 + assert int_largest - u32_3 == 4294967292 + int_smallest = int() + assert int_smallest + u32_3 == 3 + + int_too_big = int() + 4294967296 + int_too_small = int() - 1 + with assertRaises(ValueError): + assert u32_3 & int_too_big + with assertRaises(ValueError): + assert int_too_small & u32_3 + +def test_coerce_to_and_from_int() -> None: + for n in [0, 1, 255, 256, 1000, 32767, 4294967295]: + x: u32 = n + m: int = x + assert m == n + +def test_coerce_from_len_and_negative_int() -> None: + a = [1, 2, 3] + x: u32 = len(a) + assert x == 3 + n = 0 + for i in range(-2, 2): + if i < 0: + with assertRaises(ValueError, "int too large or small to convert to u32"): + y: u32 = i + else: + y = i + assert y == i + n += 1 + assert n == 2 + +def test_explicit_conversion_to_u32() -> None: + x = u32(5) + assert x == 5 + y = int() + ERROR + x = u32(y) + assert x == ERROR + n64: i64 = 233 + x = u32(n64) + assert x == 233 + n32: i32 = 234 + x = u32(n32) + assert x == 234 + z = u32(x) + assert z == 234 + n16: i16 = 231 + x = u32(n16) + assert x == 231 + n8: u8 = 232 + x = u32(n8) + assert x == 232 + +def test_explicit_conversion_overflow() -> None: + max_u32 = int() + 4294967295 + x = u32(max_u32) + assert x == 4294967295 + assert int(x) == max_u32 + + min_u32 = int() + y = u32(min_u32) + assert y == 0 + assert int(y) == min_u32 + + too_big = int() + 4294967296 + with assertRaises(ValueError): + x = u32(too_big) + + too_small = int() - 1 + with assertRaises(ValueError): + x = u32(too_small) + +def test_u32_from_large_small_literal() -> None: + x = u32(4294967295) + assert x == 4294967295 + x = u32(0) + assert x == 0 + +def test_u32_truncate_from_i64() -> None: + large = i64(2**32 + 4294967296 + 157 + int()) + x = u32(large) + assert x == 157 + small = i64(-2**32 - 4294967296 - 157 + int()) + x = u32(small) + assert x == 4294967296 - 157 + large2 = i64(2**16 + int()) + x = u32(large2) + assert x == 65536 + small2 = i64(-2**16 - 1 - int()) + x = u32(small2) + assert x == 4294901759 + +def test_u32_truncate_from_i32() -> None: + large = i32(2**20 + 2**16 + 5 + int()) + assert u32(large) == 1114117 + small = i32(-2**20 - 2**16 - 1 + int()) + assert u32(small) == 4293853183 + +def from_float(x: float) -> u32: + return u32(x) + +def test_explicit_conversion_from_float() -> None: + assert from_float(0.0) == 0 + assert from_float(1.456) == 1 + assert from_float(234.567) == 234 + assert from_float(4294967295) == 4294967295 + assert from_float(0) == 0 + assert from_float(-0.999) == 0 + # The error message could be better, but this is acceptable + with assertRaises(ValueError, "int too large or small to convert to u32"): + assert from_float(float(4294967296)) + with assertRaises(ValueError, "int too large or small to convert to u32"): + # One ulp below the lowest valid i64 value + from_float(float(-1.0)) + +def test_tuple_u32() -> None: + a: u32 = 1 + b: u32 = 2 + t = (a, b) + a, b = t + assert a == 1 + assert b == 2 + x: Any = t + tt: Tuple[u32, u32] = x + assert tt == (1, 2) + +def test_convert_u32_to_native_int() -> None: + for i in [0, 1, 255, 1000, 32767, 32768, 65535]: + x: u32 = i + assert u8(x) == (i & 0xff) + assert i16(x) == (i if i < 32768 else i - 65536) + assert i32(x) == i + assert i64(x) == i + +def test_short_int_and_digit_boundaries() -> None: + for i in [2**30 - 1, 2**30, 2**31 - 1, 2**31, 2**32 - 2]: + o: Any = i + x: u32 = o + o2: Any = x + assert o2 == i + n = i + int() + y: u32 = n + assert y == i + m: int = y + assert m == i + assert u32(n) == i + +def test_high_bit_conversions() -> None: + big: u32 = 2**31 + int() + top: u32 = 2**32 - 1 + int() + assert i64(big) == 2**31 + assert i64(top) == 2**32 - 1 + assert i32(big) == -2**31 + assert i32(top) == -1 + assert i16(top) == -1 + assert u8(top) == 255 + n16: i16 = -1 - int() + assert u32(n16) == 2**32 - 1 + n32: i32 = -2**31 + int() + assert u32(n32) == 2**31 + assert big > 1 + assert big >> 1 == 2**30 diff --git a/mypyc/test/test_irbuild.py b/mypyc/test/test_irbuild.py index 86ef4d264bbd..f442e3efb713 100644 --- a/mypyc/test/test_irbuild.py +++ b/mypyc/test/test_irbuild.py @@ -46,6 +46,7 @@ "irbuild-i32.test", "irbuild-i16.test", "irbuild-u8.test", + "irbuild-u32.test", "irbuild-u16.test", "irbuild-vec-i64.test", "irbuild-vec-misc.test", diff --git a/mypyc/test/test_run.py b/mypyc/test/test_run.py index d56b514859c9..63df8582d74f 100644 --- a/mypyc/test/test_run.py +++ b/mypyc/test/test_run.py @@ -50,6 +50,7 @@ "run-i32.test", "run-i16.test", "run-u8.test", + "run-u32.test", "run-u16.test", "run-floats.test", "run-math.test", diff --git a/mypyc/test/test_tuplename.py b/mypyc/test/test_tuplename.py index 255575e08829..f577c7e2faac 100644 --- a/mypyc/test/test_tuplename.py +++ b/mypyc/test/test_tuplename.py @@ -16,6 +16,7 @@ object_rprimitive, uint8_rprimitive, uint16_rprimitive, + uint32_rprimitive, ) @@ -27,6 +28,7 @@ def setUp(self) -> None: def test_names(self) -> None: assert RTuple([int_rprimitive, int_rprimitive]).unique_id == "T2II" assert RTuple([int64_rprimitive, int32_rprimitive]).unique_id == "T284" + assert RTuple([int32_rprimitive, uint32_rprimitive]).unique_id == "T24U4" assert RTuple([int16_rprimitive, uint16_rprimitive]).unique_id == "T22U2" assert RTuple([uint8_rprimitive]).unique_id == "T1U1" assert RTuple([list_rprimitive, object_rprimitive, self.inst_a]).unique_id == "T3OOO" diff --git a/mypyc/test/test_typeops.py b/mypyc/test/test_typeops.py index f551ae3e7453..77e12f5a5adc 100644 --- a/mypyc/test/test_typeops.py +++ b/mypyc/test/test_typeops.py @@ -17,6 +17,7 @@ str_rprimitive, uint8_rprimitive, uint16_rprimitive, + uint32_rprimitive, ) from mypyc.rt_subtype import is_runtime_subtype from mypyc.subtype import is_subtype @@ -25,6 +26,8 @@ int64_rprimitive, int32_rprimitive, int16_rprimitive, + uint32_rprimitive, + uint32_rprimitive, uint16_rprimitive, uint8_rprimitive, ] @@ -66,6 +69,15 @@ def test_int16(self) -> None: assert not is_subtype(int16_rprimitive, int64_rprimitive) assert not is_subtype(int16_rprimitive, int32_rprimitive) + def test_uint32(self) -> None: + assert is_subtype(uint32_rprimitive, uint32_rprimitive) + assert is_subtype(uint32_rprimitive, int_rprimitive) + assert not is_subtype(uint32_rprimitive, short_int_rprimitive) + assert not is_subtype(uint32_rprimitive, int64_rprimitive) + assert not is_subtype(uint32_rprimitive, int32_rprimitive) + assert not is_subtype(uint32_rprimitive, int16_rprimitive) + assert not is_subtype(uint32_rprimitive, uint16_rprimitive) + def test_uint16(self) -> None: assert is_subtype(uint16_rprimitive, uint16_rprimitive) assert is_subtype(uint16_rprimitive, int_rprimitive) diff --git a/test-data/unit/lib-stub/mypy_extensions.pyi b/test-data/unit/lib-stub/mypy_extensions.pyi index eedc2bceb921..6d0c03bbdd28 100644 --- a/test-data/unit/lib-stub/mypy_extensions.pyi +++ b/test-data/unit/lib-stub/mypy_extensions.pyi @@ -50,7 +50,37 @@ class FlexibleAlias(Generic[_T, _U]): ... class __SupportsInt(Protocol[T_co]): def __int__(self) -> int: pass -_Int = Union[int, u16, u8, i16, i32, i64] +_Int = Union[int, u8, u16, u32, i16, i32, i64] + +class u8: + def __init__(self, x: Union[_Int, str, bytes, SupportsInt], base: int = 10) -> None: ... + def __add__(self, x: u8) -> u8: ... + def __radd__(self, x: u8) -> u8: ... + def __sub__(self, x: u8) -> u8: ... + def __rsub__(self, x: u8) -> u8: ... + def __mul__(self, x: u8) -> u8: ... + def __rmul__(self, x: u8) -> u8: ... + def __floordiv__(self, x: u8) -> u8: ... + def __rfloordiv__(self, x: u8) -> u8: ... + def __mod__(self, x: u8) -> u8: ... + def __rmod__(self, x: u8) -> u8: ... + def __and__(self, x: u8) -> u8: ... + def __rand__(self, x: u8) -> u8: ... + def __or__(self, x: u8) -> u8: ... + def __ror__(self, x: u8) -> u8: ... + def __xor__(self, x: u8) -> u8: ... + def __rxor__(self, x: u8) -> u8: ... + def __lshift__(self, x: u8) -> u8: ... + def __rlshift__(self, x: u8) -> u8: ... + def __rshift__(self, x: u8) -> u8: ... + def __rrshift__(self, x: u8) -> u8: ... + def __neg__(self) -> u8: ... + def __invert__(self) -> u8: ... + def __pos__(self) -> u8: ... + def __lt__(self, x: u8) -> bool: ... + def __le__(self, x: u8) -> bool: ... + def __ge__(self, x: u8) -> bool: ... + def __gt__(self, x: u8) -> bool: ... class u16: def __init__(self, x: Union[_Int, str, bytes, SupportsInt], base: int = 10) -> None: ... @@ -82,35 +112,35 @@ class u16: def __ge__(self, x: u16) -> bool: ... def __gt__(self, x: u16) -> bool: ... -class u8: +class u32: def __init__(self, x: Union[_Int, str, bytes, SupportsInt], base: int = 10) -> None: ... - def __add__(self, x: u8) -> u8: ... - def __radd__(self, x: u8) -> u8: ... - def __sub__(self, x: u8) -> u8: ... - def __rsub__(self, x: u8) -> u8: ... - def __mul__(self, x: u8) -> u8: ... - def __rmul__(self, x: u8) -> u8: ... - def __floordiv__(self, x: u8) -> u8: ... - def __rfloordiv__(self, x: u8) -> u8: ... - def __mod__(self, x: u8) -> u8: ... - def __rmod__(self, x: u8) -> u8: ... - def __and__(self, x: u8) -> u8: ... - def __rand__(self, x: u8) -> u8: ... - def __or__(self, x: u8) -> u8: ... - def __ror__(self, x: u8) -> u8: ... - def __xor__(self, x: u8) -> u8: ... - def __rxor__(self, x: u8) -> u8: ... - def __lshift__(self, x: u8) -> u8: ... - def __rlshift__(self, x: u8) -> u8: ... - def __rshift__(self, x: u8) -> u8: ... - def __rrshift__(self, x: u8) -> u8: ... - def __neg__(self) -> u8: ... - def __invert__(self) -> u8: ... - def __pos__(self) -> u8: ... - def __lt__(self, x: u8) -> bool: ... - def __le__(self, x: u8) -> bool: ... - def __ge__(self, x: u8) -> bool: ... - def __gt__(self, x: u8) -> bool: ... + def __add__(self, x: u32) -> u32: ... + def __radd__(self, x: u32) -> u32: ... + def __sub__(self, x: u32) -> u32: ... + def __rsub__(self, x: u32) -> u32: ... + def __mul__(self, x: u32) -> u32: ... + def __rmul__(self, x: u32) -> u32: ... + def __floordiv__(self, x: u32) -> u32: ... + def __rfloordiv__(self, x: u32) -> u32: ... + def __mod__(self, x: u32) -> u32: ... + def __rmod__(self, x: u32) -> u32: ... + def __and__(self, x: u32) -> u32: ... + def __rand__(self, x: u32) -> u32: ... + def __or__(self, x: u32) -> u32: ... + def __ror__(self, x: u32) -> u32: ... + def __xor__(self, x: u32) -> u32: ... + def __rxor__(self, x: u32) -> u32: ... + def __lshift__(self, x: u32) -> u32: ... + def __rlshift__(self, x: u32) -> u32: ... + def __rshift__(self, x: u32) -> u32: ... + def __rrshift__(self, x: u32) -> u32: ... + def __neg__(self) -> u32: ... + def __invert__(self) -> u32: ... + def __pos__(self) -> u32: ... + def __lt__(self, x: u32) -> bool: ... + def __le__(self, x: u32) -> bool: ... + def __ge__(self, x: u32) -> bool: ... + def __gt__(self, x: u32) -> bool: ... class i16: def __init__(self, x: Union[_Int, str, bytes, SupportsInt], base: int = 10) -> None: ...