Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
c0baf62
Ignore .worktrees directory (#6)
BobTheBuidler Jan 17, 2026
b5dfda6
chore: sync AGENTS rules to keep contributor guidance consistent (#8)
BobTheBuidler Jan 17, 2026
f910a54
mypyc: add ascii format fast path (#17)
BobTheBuidler Jan 18, 2026
d1f542c
Merge branch 'master' into master
BobTheBuidler Jan 18, 2026
8c9cd5b
Delete AGENTS.md
BobTheBuidler Jan 18, 2026
14c890d
Update .gitignore
BobTheBuidler Jan 18, 2026
36e29de
Update str_ops.py
BobTheBuidler Jan 18, 2026
b7792b2
chore: update agent requirements to keep contributor workflow consistent
BobTheBuidler Jan 19, 2026
4976c1c
chore: update agent requirements and .gitignore to keep contributor g…
BobTheBuidler Jan 20, 2026
23f3a8b
Merge pull request #24 from BobTheBuidler/agents-update
BobTheBuidler Jan 20, 2026
d60a833
docs: add agent requirements for consistent automation
BobTheBuidler Jan 20, 2026
0f40bcd
Merge pull request #25 from BobTheBuidler/agents/add-20260120085058
BobTheBuidler Jan 20, 2026
2b0381c
chore: scope mypy/pytest workflow paths to reduce CI noise
BobTheBuidler Jan 20, 2026
6d892a2
chore: trim workflow paths to existing files
BobTheBuidler Jan 20, 2026
c91d9a7
chore(cicd): refine workflow paths
BobTheBuidler Jan 20, 2026
dfc4b9e
Merge pull request #26 from BobTheBuidler/chore/mypy-mypy-pytest-paths
BobTheBuidler Jan 20, 2026
4cf94b8
chore: clarify AGENTS remote-branch guidance for up-to-date work
BobTheBuidler Jan 21, 2026
56862d6
Merge pull request #27 from BobTheBuidler/chore/agents-remote-branch-…
BobTheBuidler Jan 21, 2026
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
8 changes: 8 additions & 0 deletions .github/workflows/build_wheels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@ name: Trigger wheel build

on:
push:
paths:
- '**/*.py'
- '**/*.pyi'
- 'pyproject.toml'
- 'setup.py'
- 'tox.ini'
- '.pre-commit-config.yaml'
- '.github/workflows/build_wheels.yml'
branches: [main, master, 'release*']
tags: ['*']

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,4 @@ test_capi
test_capi
/mypyc/lib-rt/build/
/mypyc/lib-rt/*.so
.worktrees/
18 changes: 18 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Agent Requirements

All agents must follow these rules:

1) Fully test your changes before submitting a PR (run the full suite or all relevant tests).
2) PR titles must be descriptive and follow Conventional Commits-style prefixes:
- Common: `feat:`, `fix:`, `chore:`, `refactor:`, `docs:`, `test:`, `perf:`
- Support titles: `fix(docs):`, `fix(benchmarks):`, `fix(cicd):`
3) Commit messages must follow the same Conventional Commits-style prefixes and include a short functional description plus a user-facing value proposition.
4) PR descriptions must include Summary, Rationale, and Details sections.
5) Run relevant Python tests for changes (pytest/unittest or the repo's configured runner).
6) Follow formatting/linting configured in pyproject.toml, setup.cfg, tox.ini, or ruff.toml.
7) Update dependency lockfiles when adding or removing Python dependencies.
8) If the repo uses mypyc, verify tests run against compiled extensions (not interpreted Python) and note how you confirmed.
9) Keep base image tags pinned.
10) If the branch you're assigned to work on is from a remote (ie origin/master or upstream/awesome-feature) you must ensure you fetch and pull from the remote before you begin your work.

Reference: https://www.conventionalcommits.org/en/v1.0.0/
21 changes: 19 additions & 2 deletions mypyc/irbuild/format_str_tokenizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from mypyc.irbuild.constant_fold import constant_fold_expr
from mypyc.primitives.bytes_ops import bytes_build_op
from mypyc.primitives.int_ops import int_to_str_op
from mypyc.primitives.str_ops import str_build_op, str_op
from mypyc.primitives.str_ops import ascii_op, str_build_op, str_op


@unique
Expand All @@ -42,6 +42,7 @@ class FormatOp(Enum):

STR = "s"
INT = "d"
ASCII = "a"
BYTES = "b"


Expand All @@ -53,14 +54,25 @@ def generate_format_ops(specifiers: list[ConversionSpecifier]) -> list[FormatOp]
format_ops = []
for spec in specifiers:
# TODO: Match specifiers instead of using whole_seq
if spec.whole_seq == "%s" or spec.whole_seq == "{:{}}":
# Conversion flags for str.format/f-strings (e.g. {!a}); only if no format spec.
if spec.conversion and not spec.format_spec:
if spec.conversion == "!a":
format_op = FormatOp.ASCII
else:
return None
# printf-style tokens and special f-string lowering patterns.
elif spec.whole_seq == "%s" or spec.whole_seq == "{:{}}":
format_op = FormatOp.STR
elif spec.whole_seq == "%d":
format_op = FormatOp.INT
elif spec.whole_seq == "%a":
format_op = FormatOp.ASCII
elif spec.whole_seq == "%b":
format_op = FormatOp.BYTES
# Any other non-empty spec means we can't optimize; fall back to runtime formatting.
elif spec.whole_seq:
return None
# Empty spec ("{}") defaults to str().
else:
format_op = FormatOp.STR
format_ops.append(format_op)
Expand Down Expand Up @@ -152,6 +164,11 @@ def convert_format_expr_to_str(
var_str = builder.primitive_op(int_to_str_op, [builder.accept(x)], line)
else:
var_str = builder.primitive_op(str_op, [builder.accept(x)], line)
elif format_op == FormatOp.ASCII:
if (folded := constant_fold_expr(builder, x)) is not None:
var_str = builder.load_literal_value(ascii(folded))
else:
var_str = builder.primitive_op(ascii_op, [builder.accept(x)], line)
elif format_op == FormatOp.INT:
if isinstance(folded := constant_fold_expr(builder, x), int):
var_str = builder.load_literal_value(str(folded))
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 @@ -51,6 +51,15 @@
error_kind=ERR_MAGIC,
)

# ascii(obj)
ascii_op = function_op(
name="builtins.ascii",
arg_types=[object_rprimitive],
return_type=str_rprimitive,
c_function_name="PyObject_ASCII",
error_kind=ERR_MAGIC,
)

# translate isinstance(obj, str)
isinstance_str = function_op(
name="builtins.isinstance",
Expand Down
17 changes: 17 additions & 0 deletions mypyc/test-data/irbuild-constant-fold.test
Original file line number Diff line number Diff line change
Expand Up @@ -192,9 +192,14 @@ L0:
from typing import Any, Final

FMT: Final = "{} {}"
FMT_A: Final = "{!a}"

def f() -> str:
return FMT.format(400 + 20, "roll" + "up")
def g() -> str:
return FMT_A.format("\u00e9")
def g2() -> str:
return FMT_A.format("\u2603")
[out]
def f():
r0, r1, r2, r3 :: str
Expand All @@ -204,6 +209,18 @@ L0:
r2 = ' '
r3 = CPyStr_Build(3, r0, r2, r1)
return r3
def g():
r0, r1 :: str
L0:
r0 = "'\\xe9'"
r1 = CPyStr_Build(1, r0)
return r1
def g2():
r0, r1 :: str
L0:
r0 = "'\\u2603'"
r1 = CPyStr_Build(1, r0)
return r1

[case testIntConstantFoldingFinal]
from typing import Final
Expand Down
7 changes: 6 additions & 1 deletion mypyc/test-data/irbuild-str.test
Original file line number Diff line number Diff line change
Expand Up @@ -291,14 +291,15 @@ def f(var: Union[str, NewStr], num: int) -> None:
s2 = "I am %d years old." % num
s3 = "Hi! I'm %s. I am %d years old." % (var, num)
s4 = "Float: %f" % num
s5 = "Ascii: %a" % var
[typing fixtures/typing-full.pyi]
[out]
def f(var, num):
var :: str
num :: int
r0, r1, r2, s1, r3, r4, r5, r6, s2, r7, r8, r9, r10, r11, s3, r12 :: str
r13, r14 :: object
r15, s4 :: str
r15, s4, r16, r17, r18, s5 :: str
L0:
r0 = "Hi! I'm "
r1 = '.'
Expand All @@ -320,6 +321,10 @@ L0:
r14 = PyNumber_Remainder(r12, r13)
r15 = cast(str, r14)
s4 = r15
r16 = PyObject_ASCII(var)
r17 = 'Ascii: '
r18 = CPyStr_Build(2, r17, r16)
s5 = r18
return 1

[case testDecode]
Expand Down