From ae85fd0197705701b66be06862f57f31843ed90d Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Thu, 2 Oct 2025 00:24:00 +0000 Subject: [PATCH 01/17] [mypyc] feat: support constant folding in `translate_index_expr` --- mypyc/irbuild/expression.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypyc/irbuild/expression.py b/mypyc/irbuild/expression.py index 54a101bc4961..39734f91868d 100644 --- a/mypyc/irbuild/expression.py +++ b/mypyc/irbuild/expression.py @@ -590,8 +590,8 @@ def transform_index_expr(builder: IRBuilder, expr: IndexExpr) -> Value: base = builder.accept(expr.base, can_borrow=can_borrow_base) - if isinstance(base.type, RTuple) and isinstance(index, IntExpr): - return builder.add(TupleGet(base, index.value, expr.line)) + if isinstance(base.type, RTuple) and isinstance(folded_index := constant_fold_expr(builder, index), int): + return builder.add(TupleGet(base, folded_index, expr.line)) if isinstance(index, SliceExpr): value = try_gen_slice_op(builder, base, index) From 621f95dad980bc904d58d09fff73674ca6a4d5ae Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 2 Oct 2025 00:27:50 +0000 Subject: [PATCH 02/17] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypyc/irbuild/expression.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mypyc/irbuild/expression.py b/mypyc/irbuild/expression.py index 39734f91868d..ac64822a59ed 100644 --- a/mypyc/irbuild/expression.py +++ b/mypyc/irbuild/expression.py @@ -590,7 +590,9 @@ def transform_index_expr(builder: IRBuilder, expr: IndexExpr) -> Value: base = builder.accept(expr.base, can_borrow=can_borrow_base) - if isinstance(base.type, RTuple) and isinstance(folded_index := constant_fold_expr(builder, index), int): + if isinstance(base.type, RTuple) and isinstance( + folded_index := constant_fold_expr(builder, index), int + ): return builder.add(TupleGet(base, folded_index, expr.line)) if isinstance(index, SliceExpr): From c672af6e4e5ca23ab1e393eb628ec8f607d207fa Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Wed, 1 Oct 2025 20:34:29 -0400 Subject: [PATCH 03/17] reject negatives --- mypyc/irbuild/expression.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypyc/irbuild/expression.py b/mypyc/irbuild/expression.py index ac64822a59ed..abeece35bfc0 100644 --- a/mypyc/irbuild/expression.py +++ b/mypyc/irbuild/expression.py @@ -592,7 +592,7 @@ def transform_index_expr(builder: IRBuilder, expr: IndexExpr) -> Value: if isinstance(base.type, RTuple) and isinstance( folded_index := constant_fold_expr(builder, index), int - ): + ) and folded_index >= 0: return builder.add(TupleGet(base, folded_index, expr.line)) if isinstance(index, SliceExpr): From 8f72b22c95b127aabf8caf9eae3eddcbc8ed5ce1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 2 Oct 2025 00:35:56 +0000 Subject: [PATCH 04/17] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypyc/irbuild/expression.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/mypyc/irbuild/expression.py b/mypyc/irbuild/expression.py index abeece35bfc0..e4d793b38d64 100644 --- a/mypyc/irbuild/expression.py +++ b/mypyc/irbuild/expression.py @@ -590,9 +590,11 @@ def transform_index_expr(builder: IRBuilder, expr: IndexExpr) -> Value: base = builder.accept(expr.base, can_borrow=can_borrow_base) - if isinstance(base.type, RTuple) and isinstance( - folded_index := constant_fold_expr(builder, index), int - ) and folded_index >= 0: + if ( + isinstance(base.type, RTuple) + and isinstance(folded_index := constant_fold_expr(builder, index), int) + and folded_index >= 0 + ): return builder.add(TupleGet(base, folded_index, expr.line)) if isinstance(index, SliceExpr): From be7a4e97d90fb903fd96a8e941599c13ca8e472d Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Thu, 2 Oct 2025 13:31:29 -0400 Subject: [PATCH 05/17] length check for safety --- mypyc/irbuild/expression.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypyc/irbuild/expression.py b/mypyc/irbuild/expression.py index a86927312d60..500861ca05f7 100644 --- a/mypyc/irbuild/expression.py +++ b/mypyc/irbuild/expression.py @@ -593,7 +593,7 @@ def transform_index_expr(builder: IRBuilder, expr: IndexExpr) -> Value: if ( isinstance(base.type, RTuple) and isinstance(folded_index := constant_fold_expr(builder, index), int) - and folded_index >= 0 + and 0 <= folded_index <= len(base.type.types) - 1 ): return builder.add(TupleGet(base, folded_index, expr.line)) From 37f39e7e06c33589bbbdcc0d26ee1e589e987245 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Thu, 2 Oct 2025 13:37:37 -0400 Subject: [PATCH 06/17] support negative index --- mypyc/irbuild/expression.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mypyc/irbuild/expression.py b/mypyc/irbuild/expression.py index 500861ca05f7..f6636a0e7b62 100644 --- a/mypyc/irbuild/expression.py +++ b/mypyc/irbuild/expression.py @@ -590,12 +590,12 @@ def transform_index_expr(builder: IRBuilder, expr: IndexExpr) -> Value: base = builder.accept(expr.base, can_borrow=can_borrow_base) - if ( - isinstance(base.type, RTuple) - and isinstance(folded_index := constant_fold_expr(builder, index), int) - and 0 <= folded_index <= len(base.type.types) - 1 - ): - return builder.add(TupleGet(base, folded_index, expr.line)) + if isinstance(base.type, RTuple): + folded_index = constant_fold_expr(builder, index) + if isinstance(folded_index, int): + length = len(base.type.types) + if -length <= folded_index <= length - 1: + return builder.add(TupleGet(base, folded_index, expr.line)) if isinstance(index, SliceExpr): value = try_gen_slice_op(builder, base, index) From b5a2d1a23f240c8353d5385c28f5ea21f5e54ae6 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Thu, 2 Oct 2025 13:45:42 -0400 Subject: [PATCH 07/17] support negative index in TupleGet maybe I should just compute the correct positive index value when we encounter a negative index, rather than doing this? Where's the best place to do that? Should that be with this or in a separate PR? --- mypyc/ir/ops.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mypyc/ir/ops.py b/mypyc/ir/ops.py index 76c1e07a79d5..fc326ce69163 100644 --- a/mypyc/ir/ops.py +++ b/mypyc/ir/ops.py @@ -1048,7 +1048,8 @@ def __init__(self, src: Value, index: int, line: int = -1, *, borrow: bool = Fal self.src = src self.index = index assert isinstance(src.type, RTuple), "TupleGet only operates on tuples" - assert index >= 0 + src_len = len(src.type.types) + assert -src_len <= index <= src_len - 1, f"source {src} index {index}" self.type = src.type.types[index] self.is_borrowed = borrow From fb5c2f434286b946ee018d1d382eb6226c215b27 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Thu, 2 Oct 2025 13:46:33 -0400 Subject: [PATCH 08/17] Update ops.py --- mypyc/ir/ops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypyc/ir/ops.py b/mypyc/ir/ops.py index fc326ce69163..304604d707bd 100644 --- a/mypyc/ir/ops.py +++ b/mypyc/ir/ops.py @@ -1049,7 +1049,7 @@ def __init__(self, src: Value, index: int, line: int = -1, *, borrow: bool = Fal self.index = index assert isinstance(src.type, RTuple), "TupleGet only operates on tuples" src_len = len(src.type.types) - assert -src_len <= index <= src_len - 1, f"source {src} index {index}" + assert -src_len <= index <= src_len - 1, f"source type: {src.type}\nindex: {index}" self.type = src.type.types[index] self.is_borrowed = borrow From fad9daae099f46cebb050bed0b7d25c9f5582769 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Thu, 2 Oct 2025 13:48:19 -0400 Subject: [PATCH 09/17] Update ops.py --- mypyc/ir/ops.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypyc/ir/ops.py b/mypyc/ir/ops.py index 304604d707bd..c215c8e8500a 100644 --- a/mypyc/ir/ops.py +++ b/mypyc/ir/ops.py @@ -1047,9 +1047,9 @@ def __init__(self, src: Value, index: int, line: int = -1, *, borrow: bool = Fal super().__init__(line) self.src = src self.index = index - assert isinstance(src.type, RTuple), "TupleGet only operates on tuples" + assert isinstance(src.type, RTuple), "TupleGet only operates on tuples, not {type(src.type).__name__}" src_len = len(src.type.types) - assert -src_len <= index <= src_len - 1, f"source type: {src.type}\nindex: {index}" + assert -src_len <= index <= src_len - 1, f"Index out of range.\nsource type: {src.type}\nindex: {index}" self.type = src.type.types[index] self.is_borrowed = borrow From 7cdb1bede33da339de361312e66c32a2620b5512 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 2 Oct 2025 17:50:15 +0000 Subject: [PATCH 10/17] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypyc/ir/ops.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mypyc/ir/ops.py b/mypyc/ir/ops.py index c215c8e8500a..d8fff1c8b6c7 100644 --- a/mypyc/ir/ops.py +++ b/mypyc/ir/ops.py @@ -1047,9 +1047,13 @@ def __init__(self, src: Value, index: int, line: int = -1, *, borrow: bool = Fal super().__init__(line) self.src = src self.index = index - assert isinstance(src.type, RTuple), "TupleGet only operates on tuples, not {type(src.type).__name__}" + assert isinstance( + src.type, RTuple + ), "TupleGet only operates on tuples, not {type(src.type).__name__}" src_len = len(src.type.types) - assert -src_len <= index <= src_len - 1, f"Index out of range.\nsource type: {src.type}\nindex: {index}" + assert ( + -src_len <= index <= src_len - 1 + ), f"Index out of range.\nsource type: {src.type}\nindex: {index}" self.type = src.type.types[index] self.is_borrowed = borrow From b231fb7e26ca22a6050ae6c51b0bcd549c4fe910 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Thu, 2 Oct 2025 13:58:10 -0400 Subject: [PATCH 11/17] Update ops.py --- mypyc/ir/ops.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mypyc/ir/ops.py b/mypyc/ir/ops.py index d8fff1c8b6c7..ef12697c4bf0 100644 --- a/mypyc/ir/ops.py +++ b/mypyc/ir/ops.py @@ -1051,9 +1051,9 @@ def __init__(self, src: Value, index: int, line: int = -1, *, borrow: bool = Fal src.type, RTuple ), "TupleGet only operates on tuples, not {type(src.type).__name__}" src_len = len(src.type.types) - assert ( - -src_len <= index <= src_len - 1 - ), f"Index out of range.\nsource type: {src.type}\nindex: {index}" + if index < 0: + index += src_len + assert index <= src_len - 1, f"Index out of range.\nsource type: {src.type}\nindex: {index}" self.type = src.type.types[index] self.is_borrowed = borrow From d670522d6f13fb20e7039b8a742cadf895e92ca9 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Thu, 2 Oct 2025 13:59:08 -0400 Subject: [PATCH 12/17] Update ops.py --- mypyc/ir/ops.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypyc/ir/ops.py b/mypyc/ir/ops.py index ef12697c4bf0..9a355b42d73c 100644 --- a/mypyc/ir/ops.py +++ b/mypyc/ir/ops.py @@ -1045,8 +1045,6 @@ class TupleGet(RegisterOp): def __init__(self, src: Value, index: int, line: int = -1, *, borrow: bool = False) -> None: super().__init__(line) - self.src = src - self.index = index assert isinstance( src.type, RTuple ), "TupleGet only operates on tuples, not {type(src.type).__name__}" @@ -1054,6 +1052,8 @@ def __init__(self, src: Value, index: int, line: int = -1, *, borrow: bool = Fal if index < 0: index += src_len assert index <= src_len - 1, f"Index out of range.\nsource type: {src.type}\nindex: {index}" + self.src = src + self.index = index self.type = src.type.types[index] self.is_borrowed = borrow From 850484f356c2132cc9f66077b4f99ae6aa794cdd Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Thu, 2 Oct 2025 14:01:18 -0400 Subject: [PATCH 13/17] Update ops.py --- mypyc/ir/ops.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mypyc/ir/ops.py b/mypyc/ir/ops.py index 9a355b42d73c..2a110b8b11a9 100644 --- a/mypyc/ir/ops.py +++ b/mypyc/ir/ops.py @@ -1049,11 +1049,11 @@ def __init__(self, src: Value, index: int, line: int = -1, *, borrow: bool = Fal src.type, RTuple ), "TupleGet only operates on tuples, not {type(src.type).__name__}" src_len = len(src.type.types) - if index < 0: - index += src_len - assert index <= src_len - 1, f"Index out of range.\nsource type: {src.type}\nindex: {index}" self.src = src self.index = index + if index < 0: + self.index += src_len + assert self.index <= src_len - 1, f"Index out of range.\nsource type: {src.type}\nindex: {index}" self.type = src.type.types[index] self.is_borrowed = borrow From 0dae00f6e1f3ad7fe7438a96a5ea7f70fd4d0a32 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 2 Oct 2025 18:03:43 +0000 Subject: [PATCH 14/17] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypyc/ir/ops.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mypyc/ir/ops.py b/mypyc/ir/ops.py index 2a110b8b11a9..1695267a0d55 100644 --- a/mypyc/ir/ops.py +++ b/mypyc/ir/ops.py @@ -1053,7 +1053,9 @@ def __init__(self, src: Value, index: int, line: int = -1, *, borrow: bool = Fal self.index = index if index < 0: self.index += src_len - assert self.index <= src_len - 1, f"Index out of range.\nsource type: {src.type}\nindex: {index}" + assert ( + self.index <= src_len - 1 + ), f"Index out of range.\nsource type: {src.type}\nindex: {index}" self.type = src.type.types[index] self.is_borrowed = borrow From ada6dfba3cc633b162bad46926574add8c17991b Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Thu, 2 Oct 2025 15:15:25 -0400 Subject: [PATCH 15/17] Update expression.py From d3c94afd6c5b21b1ef82e4090f89b66d144474ea Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Thu, 2 Oct 2025 16:24:07 -0400 Subject: [PATCH 16/17] Update run-strings.test --- mypyc/test-data/run-strings.test | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/mypyc/test-data/run-strings.test b/mypyc/test-data/run-strings.test index 6a62db6ee3ee..22f3a5906f66 100644 --- a/mypyc/test-data/run-strings.test +++ b/mypyc/test-data/run-strings.test @@ -151,7 +151,7 @@ def test_unicode() -> None: assert ne("\U0001f4a9foo", "\U0001f4a8foo" + str()) [case testStringOps] -from typing import List, Optional, Tuple +from typing import Final, List, Optional, Tuple from testutil import assertRaises def do_split(s: str, sep: Optional[str] = None, max_split: Optional[int] = None) -> List[str]: @@ -226,6 +226,12 @@ def contains(s: str, o: str) -> bool: def getitem(s: str, index: int) -> str: return s[index] +final_string: Final = "abc" +final_int: Final = 1 + +def getitem_folded() -> str: + return final_string[final_int] + final_string[-1] + def find(s: str, substr: str, start: Optional[int] = None, end: Optional[int] = None) -> int: if start is not None: if end is not None: @@ -263,6 +269,7 @@ def test_getitem() -> None: getitem(s, 4) with assertRaises(IndexError, "string index out of range"): getitem(s, -4) + assert getitem_folded() == "bc" def test_find() -> None: s = "abcab" From 52837bb13367372f251c67e03206fb4d8031bf4b Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Thu, 2 Oct 2025 16:26:45 -0400 Subject: [PATCH 17/17] Update run-strings.test --- mypyc/test-data/run-strings.test | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/mypyc/test-data/run-strings.test b/mypyc/test-data/run-strings.test index 22f3a5906f66..6a62db6ee3ee 100644 --- a/mypyc/test-data/run-strings.test +++ b/mypyc/test-data/run-strings.test @@ -151,7 +151,7 @@ def test_unicode() -> None: assert ne("\U0001f4a9foo", "\U0001f4a8foo" + str()) [case testStringOps] -from typing import Final, List, Optional, Tuple +from typing import List, Optional, Tuple from testutil import assertRaises def do_split(s: str, sep: Optional[str] = None, max_split: Optional[int] = None) -> List[str]: @@ -226,12 +226,6 @@ def contains(s: str, o: str) -> bool: def getitem(s: str, index: int) -> str: return s[index] -final_string: Final = "abc" -final_int: Final = 1 - -def getitem_folded() -> str: - return final_string[final_int] + final_string[-1] - def find(s: str, substr: str, start: Optional[int] = None, end: Optional[int] = None) -> int: if start is not None: if end is not None: @@ -269,7 +263,6 @@ def test_getitem() -> None: getitem(s, 4) with assertRaises(IndexError, "string index out of range"): getitem(s, -4) - assert getitem_folded() == "bc" def test_find() -> None: s = "abcab"