Skip to content

bytes and bytearray __mod__ reject valid numeric formatting #16528

Closed

Description

@bschnurr

Summary

The bytes.__mod__ and bytearray.__mod__ signatures introduced in #16502 reject valid numeric %-formatting. Besides false-positive operator diagnostics, this causes a str | bytes formatting result to become str | Unknown in Pyright.

The current parameter type appears to describe %b/%s arguments rather than the full set of supported formatting conversions:

def __mod__(self, value: Buffer | SupportsBytes | tuple[Buffer | SupportsBytes, ...], /) -> bytes: ...
# bytearray has the same parameter type, returning bytearray.

Minimal repro

from typing import reveal_type


def format_bytes(number: int, value: bytes):
    reveal_type(b"%d" % number)                 # Expected: bytes; observed: Unknown + operator error
    reveal_type(b"%b:%d" % (value, number))     # Expected: bytes; observed: Unknown + operator error
    reveal_type(b"%b" % value)                 # bytes, no error (control)
    reveal_type(bytearray(b"%d") % number)     # Expected: bytearray; observed: Unknown + operator error


def format_union(fmt: str | bytes, value: bytes, number: int):
    result = fmt % (value, number)
    reveal_type(result)                        # Expected: str | bytes; observed: str | Unknown

Observed with Pyright 1.1.414, Python target 3.14, and typeshed revision 6dcbc4c3b0efddc3cb46daa4e5bbe7520979bd1a (the bundled builtins.pyi was verified to match upstream exactly). Current upstream main still has these signatures at the time of reporting. Mypy comparison was not performed because it is not installed in the available environment.

Representative diagnostics:

Operator "%" not supported for types "Literal[b"%d"]" and "int"
Operator "%" not supported for types "Literal[b"%b:%d"]" and "tuple[bytes, int]"
Operator "%" not supported for types "bytearray" and "int"
Operator "%" not supported for types "str | bytes" and "tuple[bytes, int]"
  Operator "%" not supported for types "bytes" and "tuple[bytes, int]"

These are reportOperatorIssue diagnostics. The new bytes restriction excludes int, so the bytes branch fails and produces Unknown.

Runtime behavior and specification

Executed with CPython 3.14.7:

print(b"%d" % 3)                   # b'3'
print(b"%b:%d" % (b"path", 3))     # b'path:3'
result = bytearray(b"%d") % 3
print(repr(result), type(result).__name__)  # bytearray(b'3') bytearray

PEP 461 explicitly supports numeric formatting codes for bytes and bytearray. %a/%r also support arbitrary objects, and the accepted argument type depends on the format string.

Suggested correction

Without format-string-sensitive typing, accept object while retaining precise return types, analogous to str.__mod__:

# bytes
def __mod__(self, value: object, /) -> bytes: ...

# bytearray
def __mod__(self, value: object, /) -> bytearray: ...

This does not require reverting the corrected bytearray return type or introducing Any into the result.

Activity

  1. srittau commented on Oct 14, 2026

    @srittau
    Collaborator

    Yeah, that was a bit of a brain knot on my part.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions