Skip to main content

types-bits

u1..u10 and i1..i10 as fully materialized Literal unions. Generated .pyi, zero runtime cost, exhaustively checked.

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from types_bits import u4

x: u4 = 15  # ok
y: u4 = 16  # error, on every checker

camas runs the gate; camas --list enumerates it. CI fans the same tree out over a runner axis, with both matrix axes emitted from tasks.py. camas bench and camas bench_shapes regenerate everything below into bench/results/.

Runtime cost

Zero. The stub is a PEP 484 .pyi behind TYPE_CHECKING. import types_bits loads two modules and no stdlib (tests/test_runtime.py). Every number below is type-checker wall clock, per check run.

Check cost by width

Seconds, cold, best of one, Python 3.14.1 / WSL2. Encoding flat (uN: TypeAlias = Literal[0, ..., 2**N-1]), declaring u1..uN (so a probe at N bits holds 2^(N+1)-2 literals) and widening each into the next.

bits mypy pyright basedpyright ty pyrefly zuban
8 1.54 0.60 0.72 0.07 0.27 0.10
10 1.53 0.62 0.65 0.07 0.26 0.11
12 1.58 0.59 0.70 0.11 0.32 0.28
14 1.63 0.54 0.63 0.20 0.26 3.25
16 2.58 0.55 0.60 0.34 0.39 48.73

Shipped library (u1..u10 + i1..i10): mypy 1.51, basedpyright 1.28, pyright 1.08, pyrefly 0.23, zuban 0.08, ty 0.06 — matching the uN: TypeAlias = int control.

xychart-beta
    title "16 bits"
    x-axis [zuban, mypy, basedpyright, pyright, pyrefly, ty]
    y-axis "seconds" 0 --> 55
    bar [48.73, 2.58, 0.60, 0.55, 0.39, 0.34]

zuban, same sweep — 12x then 15x per extra 2 bits past 12:

xychart-beta
    title "zuban vs width"
    x-axis "bits" [8, 10, 12, 14, 16]
    y-axis "seconds" 0 --> 55
    line [0.10, 0.11, 0.28, 3.25, 48.73]

The other five, 0–3s axis. Rising line is mypy; flat cluster is pyright, basedpyright, pyrefly, ty:

xychart-beta
    title "mypy, pyright, basedpyright, pyrefly, ty"
    x-axis "bits" [8, 10, 12, 14, 16]
    y-axis "seconds" 0 --> 3
    line [1.54, 1.53, 1.58, 1.63, 2.58]
    line [0.60, 0.62, 0.59, 0.54, 0.55]
    line [0.72, 0.65, 0.70, 0.63, 0.60]
    line [0.27, 0.26, 0.32, 0.26, 0.39]
    line [0.07, 0.07, 0.11, 0.20, 0.34]

Fixed vs marginal cost

u(N-1) and u(N) declared in every row; only the use count varies. Italic rows are the uN: TypeAlias = int control.

10 bits — indistinguishable from int:

uses mypy pyright basedpyright ty pyrefly zuban
declared, unused 1.23 0.48 0.64 0.03 0.22 0.07
control 1.39 0.48 0.65 0.06 0.22 0.08
1 widening 1.24 0.51 0.60 0.05 0.22 0.10
10 widenings 1.20 0.53 0.59 0.07 0.25 0.16

16 bits — 98,304 literals:

uses mypy pyright basedpyright ty pyrefly zuban
declared, unused 2.01 0.53 0.57 0.22 0.29 0.20
control 1.19 0.47 0.55 0.05 0.21 0.08
1 assignment 1.96 0.56 0.61 0.21 0.33 0.21
1 widening 1.94 0.52 0.60 0.21 0.28 34.90
10 widenings 2.19 0.56 0.65 0.26 0.29 timeout (>180s)
  • Declaration: fixed per check run, ~linear in literals (~8 µs/literal on mypy). Not per importing file, not per use.
  • Assignment: free. Enumerated membership is a hash lookup.
  • Widening: free except zuban at width.

Widening

Cost tracks the narrow operand, not the wide one. Wide side fixed at u16, one widening:

narrow zuban mypy ty
u1 0.18 2.54 0.20
u4 0.18 2.46 0.15
u8 0.16 2.67 0.21
u12 0.76 2.50 0.17
u15 35.21 2.80 0.18

Measured under the PEP 695 spelling; no task reproduces this sweep.

Both operands must be large. At the 10-bit ceiling the worst case (u9u10) is 0.10s. Annotating a boundary at one width, so callers assign literals rather than widen between adjacent wide aliases, avoids the shape entirely.

Encodings

encoding form result
flat Literal[0, ..., 2**N-1] shipped; fastest, portable
nested Literal[u9, 512, ...] legal per PEP 586; pyrefly rejects past ~10 levels (Invalid type inside literal, int, 1 error at 10 bits → 7 at 16), ty at 16
union u9 | Literal[512, ...] ty 44.24s at 14 bits vs 0.17s flat
annotated Annotated[Literal[...], Ge, Le] tracks flat within noise to 14 bits
opaque int control

Measured under the PEP 695 spelling, before the PEP 613 switch; camas bench_full regenerates.

xychart-beta
    title "ty at 14 bits by encoding"
    x-axis [union, nested, annotated, flat]
    y-axis "seconds" 0 --> 46
    bar [44.24, 0.23, 0.22, 0.17]

Runtime tier

PEP 562 module __getattr__ resolves the same names to Annotated[int, Ge(lo), Le(hi)] (PEP 593), the shape annotated-types consumers read. tests/test_runtime.py pins it against the static bounds for all 20 widths. Needs the rt extra; annotated_types imports on first attribute access.

from pydantic import TypeAdapter
from types_bits import u8  # Annotated[int, Ge(0), Le(255)] at runtime

TypeAdapter(u8).validate_python(256)  # ValidationError

Prior art

range-typed-integers defines u8 = NewType('u8', Annotated[int, ValueRange(0, 255)]) for the byte widths u8..u64 / i8..i64.

range-typed-integers types-bits
carrier NewType over Annotated[int, ValueRange] Literal enumeration
bound enforced by runtime u8_checked() / check_int(), raising IntegerBoundError the type checker
a: u8 = 12 mypy error — int is not u8; requires u8(12) ok
a: u8 = 900 mypy error, identical to the line above error, and distinguished
u8(900) accepted statically n/a
widths u8..u64 u1..u10

Verified against every checker in the gate: mypy reports the same Incompatible types in assignment (expression has type "int", variable has type "u8") on the in-range and out-of-range lines alike. The range is metadata no checker reads (PEP 746 would not change this). ValueRange is O(1) per type, so it reaches u64; enumeration is O(2^N), so it stops at u10.

PEPs

No PEP provides bounded integers.

PEP Status Relevance
586 – Literal Types Final The mechanism. Calls Literal insufficient for numpy-style numeric code and defers integer generics. Permits the nested form pyrefly rejects.
593 – Annotated Final Metadata channel for the runtime tier.
613 – Explicit Type Aliases Final uN: TypeAlias = ... in the stub; why the floor is 3.10.
695 – Type Parameter Syntax Final type uN = ... reads better, but mypy rejects a type statement under --python-version 3.11 — fatally, errors prevented further checking — so it would pin the floor at 3.12. The other five accept it at a 3.10 target.
561 – Packaging Type Information Final py.typed; why the stub ships in the wheel.
562 – Module __getattr__ Final One name, two tiers.
649 / 749 – Deferred Annotations Final (3.14) Guarded import gets cheaper.
746 – Type checking Annotated metadata Draft, targets 3.15 Lets a checker verify metadata suits its type. Does not make any checker enforce Ge/Le.

fixtures/reject.py covers the arithmetic case: every checker rejects u4 + u4 where a u4 is required. mypy widens the sum to int; ty and zuban widen it to Literal[0, ..., 30].

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

types_bits-0.2.0.tar.gz (119.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

types_bits-0.2.0-py3-none-any.whl (13.7 kB view details)

Uploaded Python 3

File details

Details for the file types_bits-0.2.0.tar.gz.

File metadata

  • Download URL: types_bits-0.2.0.tar.gz
  • Upload date:
  • Size: 119.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for types_bits-0.2.0.tar.gz
Algorithm Hash digest
SHA256 0e9a45d6614551043880e5f47c70981b39d78bc1c2a018d1ad4324bccf60079c
MD5 a1d0afe8a966c89af9887c531615d9a7
BLAKE2b-256 b3f62a6f942fd4b9324fa2cead74f7b85a21ccbdd0ddddce6de87bf00ac528e7

See more details on using hashes here.

File details

Details for the file types_bits-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: types_bits-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 13.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for types_bits-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 810b8e773b0ffd80fb589185abc21641fddba234a8ebbb7ce55f3f5d6dc8cf6d
MD5 e92b46030b6c6808cc5cb75b4dfdef83
BLAKE2b-256 a541cf41a0415d18b66378abc956034e417acccef3462888c173c8d53ed4ff87

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page