From c0baf62f713a5d1781862626bc0058bdef6765f6 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Fri, 16 Jan 2026 22:12:39 -0400 Subject: [PATCH 1/9] Ignore .worktrees directory (#6) --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 9c325f3e29f8..880418969bd2 100644 --- a/.gitignore +++ b/.gitignore @@ -51,6 +51,7 @@ include/ pyvenv.cfg .tox +.worktrees pip-wheel-metadata From b5dfda62f4e05eea475fe6b1a40b7da76ca9b96d Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Sat, 17 Jan 2026 00:28:47 -0400 Subject: [PATCH 2/9] chore: sync AGENTS rules to keep contributor guidance consistent (#8) --- .gitignore | 2 +- AGENTS.md | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 AGENTS.md diff --git a/.gitignore b/.gitignore index 880418969bd2..ed1e1ff7d572 100644 --- a/.gitignore +++ b/.gitignore @@ -51,7 +51,6 @@ include/ pyvenv.cfg .tox -.worktrees pip-wheel-metadata @@ -61,3 +60,4 @@ test_capi test_capi /mypyc/lib-rt/build/ /mypyc/lib-rt/*.so +.worktrees/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000000..2855e173a207 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,17 @@ +# 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. + +Reference: https://www.conventionalcommits.org/en/v1.0.0/ From f910a54fd5ee55e2876dd38258f0202c61f7d9a8 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Sun, 18 Jan 2026 10:19:01 -0400 Subject: [PATCH 3/9] mypyc: add ascii format fast path (#17) * mypyc: add ascii format fast path * mypyc: extend ascii constant-fold tests --- mypyc/irbuild/format_str_tokenizer.py | 34 +++++++++++++++----- mypyc/primitives/str_ops.py | 11 ++++++- mypyc/test-data/irbuild-constant-fold.test | 36 ++++++++++++++++++++++ mypyc/test-data/irbuild-str.test | 7 ++++- 4 files changed, 79 insertions(+), 9 deletions(-) diff --git a/mypyc/irbuild/format_str_tokenizer.py b/mypyc/irbuild/format_str_tokenizer.py index 5a35900006d2..d34ecfe00801 100644 --- a/mypyc/irbuild/format_str_tokenizer.py +++ b/mypyc/irbuild/format_str_tokenizer.py @@ -12,7 +12,7 @@ ) from mypy.errors import Errors from mypy.messages import MessageBuilder -from mypy.nodes import Context, Expression, StrExpr +from mypy.nodes import Context, Expression from mypy.options import Options from mypyc.ir.ops import Integer, Value from mypyc.ir.rtypes import ( @@ -23,9 +23,10 @@ is_str_rprimitive, ) from mypyc.irbuild.builder import IRBuilder +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 @@ -41,6 +42,7 @@ class FormatOp(Enum): STR = "s" INT = "d" + ASCII = "a" BYTES = "b" @@ -52,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) @@ -143,16 +156,23 @@ def convert_format_expr_to_str( for x, format_op in zip(exprs, format_ops): node_type = builder.node_type(x) if format_op == FormatOp.STR: - if is_str_rprimitive(node_type) or isinstance( - x, StrExpr - ): # NOTE: why does mypyc think our fake StrExprs are not str rprimitives? + if isinstance(folded := constant_fold_expr(builder, x), str): + var_str = builder.load_literal_value(folded) + elif is_str_rprimitive(node_type): var_str = builder.accept(x) elif is_int_rprimitive(node_type) or is_short_int_rprimitive(node_type): 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 is_int_rprimitive(node_type) or is_short_int_rprimitive(node_type): + if isinstance(folded := constant_fold_expr(builder, x), int): + var_str = builder.load_literal_value(str(folded)) + elif is_int_rprimitive(node_type) or is_short_int_rprimitive(node_type): var_str = builder.primitive_op(int_to_str_op, [builder.accept(x)], line) else: return None diff --git a/mypyc/primitives/str_ops.py b/mypyc/primitives/str_ops.py index f6d3f722dd7b..2f217b77ad67 100644 --- a/mypyc/primitives/str_ops.py +++ b/mypyc/primitives/str_ops.py @@ -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", @@ -180,7 +189,7 @@ name="rfind", arg_types=str_find_types[0 : i + 2], return_type=int_rprimitive, - c_function_name=str_find_functions[i], + c_function_name=str_rfind_functions[i], extra_int_constants=str_rfind_constants[i] + [(-1, c_int_rprimitive)], error_kind=ERR_MAGIC, ) diff --git a/mypyc/test-data/irbuild-constant-fold.test b/mypyc/test-data/irbuild-constant-fold.test index cd953c84c541..ba6cc3b73ae1 100644 --- a/mypyc/test-data/irbuild-constant-fold.test +++ b/mypyc/test-data/irbuild-constant-fold.test @@ -186,6 +186,42 @@ L0: big5 = r4 return 1 +[case testConstantFoldFormatArgs] +# This only tests that the callee and args are constant folded, +# it is not intended to test the result. +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 +L0: + r0 = CPyTagged_Str(840) + r1 = 'rollup' + 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 X: Final = 5 diff --git a/mypyc/test-data/irbuild-str.test b/mypyc/test-data/irbuild-str.test index ee618bb34f65..16aed004e02f 100644 --- a/mypyc/test-data/irbuild-str.test +++ b/mypyc/test-data/irbuild-str.test @@ -291,6 +291,7 @@ 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): @@ -298,7 +299,7 @@ def f(var, num): 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 = '.' @@ -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] From 8c9cd5bb29d2a1507738f7b00af01794e35696ac Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Sun, 18 Jan 2026 10:41:07 -0400 Subject: [PATCH 4/9] Delete AGENTS.md --- AGENTS.md | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 2855e173a207..000000000000 --- a/AGENTS.md +++ /dev/null @@ -1,17 +0,0 @@ -# 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. - -Reference: https://www.conventionalcommits.org/en/v1.0.0/ From 14c890d5509ace66ed6e870aa9b8ae7d87a59db3 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Sun, 18 Jan 2026 10:41:34 -0400 Subject: [PATCH 5/9] Update .gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index ed1e1ff7d572..9c325f3e29f8 100644 --- a/.gitignore +++ b/.gitignore @@ -60,4 +60,3 @@ test_capi test_capi /mypyc/lib-rt/build/ /mypyc/lib-rt/*.so -.worktrees/ From 36e29deb45db7c51d3344d37b238e0446048be0e Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Sun, 18 Jan 2026 10:44:40 -0400 Subject: [PATCH 6/9] Update str_ops.py --- mypyc/primitives/str_ops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypyc/primitives/str_ops.py b/mypyc/primitives/str_ops.py index 2f217b77ad67..6a178accca8a 100644 --- a/mypyc/primitives/str_ops.py +++ b/mypyc/primitives/str_ops.py @@ -189,7 +189,7 @@ name="rfind", arg_types=str_find_types[0 : i + 2], return_type=int_rprimitive, - c_function_name=str_rfind_functions[i], + c_function_name=str_find_functions[i], extra_int_constants=str_rfind_constants[i] + [(-1, c_int_rprimitive)], error_kind=ERR_MAGIC, ) From b7792b286d3f96c29a5a7e31b0a2641a8430c9ae Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Mon, 19 Jan 2026 19:48:29 -0400 Subject: [PATCH 7/9] chore: update agent requirements to keep contributor workflow consistent --- .gitignore | 1 + AGENTS.md | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 AGENTS.md diff --git a/.gitignore b/.gitignore index 9c325f3e29f8..ed1e1ff7d572 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,4 @@ test_capi test_capi /mypyc/lib-rt/build/ /mypyc/lib-rt/*.so +.worktrees/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000000..cfe9f6a1195d --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,20 @@ +# 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) All mypy configuration (flags, overrides, per-module ignores, and file targets) should go in pyproject.toml. Do not split config across CLI args, mypy.ini, setup.cfg, or workflow steps. +10) Centralize pytest settings (flags, markers, ignore patterns, and targets) in pyproject.toml, pytest.ini, setup.cfg, or tox.ini; workflows/hooks should call pytest without inline args. +11) For unittest workflows, prefer python -m unittest without inline args; if discovery arguments are required, centralize them in a single script and call that from CI. +12) Keep base image tags pinned. + +Reference: https://www.conventionalcommits.org/en/v1.0.0/ From 4976c1cbaf73ace63ecb14ca0e1852206fe944ee Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Tue, 20 Jan 2026 02:25:00 -0400 Subject: [PATCH 8/9] chore: update agent requirements and .gitignore to keep contributor guidance current --- AGENTS.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index cfe9f6a1195d..9f07e9a7b267 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,14 +7,15 @@ All agents must follow these rules: - 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. +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) All mypy configuration (flags, overrides, per-module ignores, and file targets) should go in pyproject.toml. Do not split config across CLI args, mypy.ini, setup.cfg, or workflow steps. -10) Centralize pytest settings (flags, markers, ignore patterns, and targets) in pyproject.toml, pytest.ini, setup.cfg, or tox.ini; workflows/hooks should call pytest without inline args. -11) For unittest workflows, prefer python -m unittest without inline args; if discovery arguments are required, centralize them in a single script and call that from CI. -12) Keep base image tags pinned. +9) Maximize the use of caching in GitHub workflow files to minimize run duration. +10) Use one of `paths` or `paths-ignore` in every workflow file to make sure workflows only run when required. +11) All mypy configuration (flags, overrides, per-module ignores, and file targets) should go in pyproject.toml. Do not split config across CLI args, mypy.ini, setup.cfg, or workflow steps. +12) Centralize pytest settings (flags, markers, ignore patterns, and targets) in pyproject.toml, pytest.ini, setup.cfg, or tox.ini; workflows/hooks should call pytest without inline args. +13) Keep base image tags pinned. Reference: https://www.conventionalcommits.org/en/v1.0.0/ From d60a83340068010a75e60ed6cbdda7b8009d7b24 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Tue, 20 Jan 2026 04:56:45 -0400 Subject: [PATCH 9/9] docs: add agent requirements for consistent automation --- AGENTS.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 9f07e9a7b267..2855e173a207 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,10 +12,6 @@ All agents must follow these rules: 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) Maximize the use of caching in GitHub workflow files to minimize run duration. -10) Use one of `paths` or `paths-ignore` in every workflow file to make sure workflows only run when required. -11) All mypy configuration (flags, overrides, per-module ignores, and file targets) should go in pyproject.toml. Do not split config across CLI args, mypy.ini, setup.cfg, or workflow steps. -12) Centralize pytest settings (flags, markers, ignore patterns, and targets) in pyproject.toml, pytest.ini, setup.cfg, or tox.ini; workflows/hooks should call pytest without inline args. -13) Keep base image tags pinned. +9) Keep base image tags pinned. Reference: https://www.conventionalcommits.org/en/v1.0.0/