From 0a558002fac876e43188db153bf4e4e4636b0e18 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Wed, 30 Jul 2025 13:58:17 -0400 Subject: [PATCH 01/12] [wip] [mypyc] feat: quasi-constant folding for DictExpr and TupleExpr --- mypyc/irbuild/constant_fold.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mypyc/irbuild/constant_fold.py b/mypyc/irbuild/constant_fold.py index 12a4b15dd40c..af2b89e2a149 100644 --- a/mypyc/irbuild/constant_fold.py +++ b/mypyc/irbuild/constant_fold.py @@ -30,8 +30,8 @@ from mypyc.irbuild.util import bytes_from_str # All possible result types of constant folding -ConstantValue = Union[int, float, complex, str, bytes] -CONST_TYPES: Final = (int, float, complex, str, bytes) +ConstantValue = Union[int, float, complex, str, bytes, tuple] +CONST_TYPES: Final = (int, float, complex, str, bytes, tuple) def constant_fold_expr(builder: IRBuilder, expr: Expression) -> ConstantValue | None: @@ -72,6 +72,10 @@ def constant_fold_expr(builder: IRBuilder, expr: Expression) -> ConstantValue | value = constant_fold_expr(builder, expr.expr) if value is not None and not isinstance(value, bytes): return constant_fold_unary_op(expr.op, value) + elif isinstance(expr, TupleExpr): + folded = tuple(constant_fold_expr(item) for item in expr.items) + if None not in folded: + return folded return None From de703acc062cefad0b338d2387e82a76d4fb32b3 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Wed, 30 Jul 2025 14:02:02 -0400 Subject: [PATCH 02/12] Update constant_fold.py --- mypyc/irbuild/constant_fold.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/mypyc/irbuild/constant_fold.py b/mypyc/irbuild/constant_fold.py index af2b89e2a149..1047198e9200 100644 --- a/mypyc/irbuild/constant_fold.py +++ b/mypyc/irbuild/constant_fold.py @@ -30,8 +30,8 @@ from mypyc.irbuild.util import bytes_from_str # All possible result types of constant folding -ConstantValue = Union[int, float, complex, str, bytes, tuple] -CONST_TYPES: Final = (int, float, complex, str, bytes, tuple) +ConstantValue = Union[int, float, complex, str, bytes, tuple, dict] +CONST_TYPES: Final = (int, float, complex, str, bytes, tuple, dict) def constant_fold_expr(builder: IRBuilder, expr: Expression) -> ConstantValue | None: @@ -76,6 +76,17 @@ def constant_fold_expr(builder: IRBuilder, expr: Expression) -> ConstantValue | folded = tuple(constant_fold_expr(item) for item in expr.items) if None not in folded: return folded + elif isinstance(expr, DictExpr): + # NOTE: the builder can't simply use a dict constant like it can with other constants, since dicts are mutable. + # TODO: make the builder load the dict 'constant' by calling copy on a prebuilt constant template instead of building from scratch each time + folded = { + constant_fold_expr(key): constant_fold_expr(value) + for key, value in expr.items + } + if len(folded) == len(expr.items) and None not in folded.keys() and None not in folded.values(): + return folded + + # TODO use a placeholder instead of None so we can include None in folded tuples/dicts return None From 8b40da1440b4dd31db7986d7ce56c67aa82afa4c Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Wed, 30 Jul 2025 14:02:30 -0400 Subject: [PATCH 03/12] Update constant_fold.py --- mypyc/irbuild/constant_fold.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mypyc/irbuild/constant_fold.py b/mypyc/irbuild/constant_fold.py index 1047198e9200..3fa86c7afa30 100644 --- a/mypyc/irbuild/constant_fold.py +++ b/mypyc/irbuild/constant_fold.py @@ -16,6 +16,7 @@ from mypy.nodes import ( BytesExpr, ComplexExpr, + DictExpr, Expression, FloatExpr, IntExpr, @@ -23,6 +24,7 @@ NameExpr, OpExpr, StrExpr, + TupleExpr, UnaryExpr, Var, ) From 9be20fe142c621b8379d0ac1372e444f4a52aa10 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 30 Jul 2025 18:03:31 +0000 Subject: [PATCH 04/12] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypyc/irbuild/constant_fold.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/mypyc/irbuild/constant_fold.py b/mypyc/irbuild/constant_fold.py index 3fa86c7afa30..c58729829ac3 100644 --- a/mypyc/irbuild/constant_fold.py +++ b/mypyc/irbuild/constant_fold.py @@ -81,13 +81,14 @@ def constant_fold_expr(builder: IRBuilder, expr: Expression) -> ConstantValue | elif isinstance(expr, DictExpr): # NOTE: the builder can't simply use a dict constant like it can with other constants, since dicts are mutable. # TODO: make the builder load the dict 'constant' by calling copy on a prebuilt constant template instead of building from scratch each time - folded = { - constant_fold_expr(key): constant_fold_expr(value) - for key, value in expr.items - } - if len(folded) == len(expr.items) and None not in folded.keys() and None not in folded.values(): + folded = {constant_fold_expr(key): constant_fold_expr(value) for key, value in expr.items} + if ( + len(folded) == len(expr.items) + and None not in folded.keys() + and None not in folded.values() + ): return folded - + # TODO use a placeholder instead of None so we can include None in folded tuples/dicts return None From 37821b0d76c69b22d1a79551d8cd47904d566da9 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Wed, 30 Jul 2025 14:08:07 -0400 Subject: [PATCH 05/12] Update constant_fold.py --- mypyc/irbuild/constant_fold.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypyc/irbuild/constant_fold.py b/mypyc/irbuild/constant_fold.py index c58729829ac3..32270c5f574a 100644 --- a/mypyc/irbuild/constant_fold.py +++ b/mypyc/irbuild/constant_fold.py @@ -75,13 +75,13 @@ def constant_fold_expr(builder: IRBuilder, expr: Expression) -> ConstantValue | if value is not None and not isinstance(value, bytes): return constant_fold_unary_op(expr.op, value) elif isinstance(expr, TupleExpr): - folded = tuple(constant_fold_expr(item) for item in expr.items) + folded = tuple(constant_fold_expr(builder, item_expr) for item_expr in expr.items) if None not in folded: return folded elif isinstance(expr, DictExpr): # NOTE: the builder can't simply use a dict constant like it can with other constants, since dicts are mutable. # TODO: make the builder load the dict 'constant' by calling copy on a prebuilt constant template instead of building from scratch each time - folded = {constant_fold_expr(key): constant_fold_expr(value) for key, value in expr.items} + folded = {constant_fold_expr(builder, key_expr): constant_fold_expr(builder, value_expr) for key_expr, value_expr in expr.items} if ( len(folded) == len(expr.items) and None not in folded.keys() From 23aa56da71095c919c830a6daf6bd54a61e321d4 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Wed, 30 Jul 2025 14:08:49 -0400 Subject: [PATCH 06/12] Update constant_fold.py --- mypyc/irbuild/constant_fold.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypyc/irbuild/constant_fold.py b/mypyc/irbuild/constant_fold.py index 32270c5f574a..0ae568a4b5f2 100644 --- a/mypyc/irbuild/constant_fold.py +++ b/mypyc/irbuild/constant_fold.py @@ -10,7 +10,7 @@ from __future__ import annotations -from typing import Final, Union +from typing import Any, Final, Union from mypy.constant_fold import constant_fold_binary_op, constant_fold_unary_op from mypy.nodes import ( @@ -32,7 +32,7 @@ from mypyc.irbuild.util import bytes_from_str # All possible result types of constant folding -ConstantValue = Union[int, float, complex, str, bytes, tuple, dict] +ConstantValue = Union[int, float, complex, str, bytes, tuple[Any, ...], dict[Any, Any]] CONST_TYPES: Final = (int, float, complex, str, bytes, tuple, dict) From b0ed9b3ad9be26767379f4ec92c3933183c09773 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 30 Jul 2025 18:09:31 +0000 Subject: [PATCH 07/12] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypyc/irbuild/constant_fold.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mypyc/irbuild/constant_fold.py b/mypyc/irbuild/constant_fold.py index 0ae568a4b5f2..103c77b4193e 100644 --- a/mypyc/irbuild/constant_fold.py +++ b/mypyc/irbuild/constant_fold.py @@ -81,7 +81,10 @@ def constant_fold_expr(builder: IRBuilder, expr: Expression) -> ConstantValue | elif isinstance(expr, DictExpr): # NOTE: the builder can't simply use a dict constant like it can with other constants, since dicts are mutable. # TODO: make the builder load the dict 'constant' by calling copy on a prebuilt constant template instead of building from scratch each time - folded = {constant_fold_expr(builder, key_expr): constant_fold_expr(builder, value_expr) for key_expr, value_expr in expr.items} + folded = { + constant_fold_expr(builder, key_expr): constant_fold_expr(builder, value_expr) + for key_expr, value_expr in expr.items + } if ( len(folded) == len(expr.items) and None not in folded.keys() From 1806d8598b8a671827b550a7a98cea3bc00cf7f7 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Wed, 30 Jul 2025 14:10:30 -0400 Subject: [PATCH 08/12] Update constant_fold.py --- mypyc/irbuild/constant_fold.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypyc/irbuild/constant_fold.py b/mypyc/irbuild/constant_fold.py index 103c77b4193e..0ddeca5ed299 100644 --- a/mypyc/irbuild/constant_fold.py +++ b/mypyc/irbuild/constant_fold.py @@ -103,7 +103,7 @@ def constant_fold_binary_op_extended( mypy cannot use constant folded bytes easily so it's simpler to only support them in mypyc. """ - if not isinstance(left, bytes) and not isinstance(right, bytes): + if not isinstance(left, (bytes, tuple, dict)) and not isinstance(right, (bytes, tuple, dict)): return constant_fold_binary_op(op, left, right) if op == "+" and isinstance(left, bytes) and isinstance(right, bytes): From 8c6be0555f0978cf8e4adf22b9af787cd2864823 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Wed, 30 Jul 2025 14:12:41 -0400 Subject: [PATCH 09/12] Update builder.py --- mypyc/irbuild/builder.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/mypyc/irbuild/builder.py b/mypyc/irbuild/builder.py index ec3c1b1b1f3c..b75061992596 100644 --- a/mypyc/irbuild/builder.py +++ b/mypyc/irbuild/builder.py @@ -601,7 +601,7 @@ def load_type_var(self, name: str, line: int) -> Value: ) ) - def load_literal_value(self, val: int | str | bytes | float | complex | bool) -> Value: + def load_literal_value(self, val: int | str | bytes | float | complex | bool | tuple[Any, ...], dict[Any, Any]) -> Value: """Load value of a final name, class-level attribute, or constant folded expression.""" if isinstance(val, bool): if val: @@ -618,6 +618,12 @@ def load_literal_value(self, val: int | str | bytes | float | complex | bool) -> return self.builder.load_bytes(val) elif isinstance(val, complex): return self.builder.load_complex(val) + elif isinstance(val, tuple): + # TODO: validate this code path + return self.builder.load_tuple(val) + elif isinstance(val, dict): + # TODO: validate this code path + return self.builder.load_dict(val) else: assert False, "Unsupported literal value" From 325a751669ca5c8a8dec2506a8ef2be13f7ee714 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Wed, 30 Jul 2025 14:17:21 -0400 Subject: [PATCH 10/12] Update ll_builder.py --- mypyc/irbuild/ll_builder.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index a5e28268efed..b1e05dd45c6a 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -8,7 +8,7 @@ import sys from collections.abc import Sequence -from typing import Callable, Final, Optional +from typing import Any, Callable, Final, Optional from mypy.argmap import map_actuals_to_formals from mypy.nodes import ARG_POS, ARG_STAR, ARG_STAR2, ArgKind @@ -121,6 +121,7 @@ pointer_rprimitive, short_int_rprimitive, str_rprimitive, + tuple_rprimitive, ) from mypyc.irbuild.util import concrete_arg_kind from mypyc.options import CompilerOptions @@ -1281,6 +1282,12 @@ def load_complex(self, value: complex) -> Value: """Load a complex literal value.""" return self.add(LoadLiteral(value, object_rprimitive)) + def load_tuple(self, value: tuple[Any, ...]) -> Value: # should this be RTuple? conditional RTuple when length is known? + return self.add(LoadLiteral(value, tuple_rprimitive)) + + def load_dict(self, value: dict[Any, Any]) -> Value: + return self.add(LoadLiteral(value, dict_rprimitive)) + def load_static_checked( self, typ: RType, From 10820e821480ae9d0307755b89309b7e0b807243 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Wed, 30 Jul 2025 14:17:48 -0400 Subject: [PATCH 11/12] Update builder.py --- mypyc/irbuild/builder.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/mypyc/irbuild/builder.py b/mypyc/irbuild/builder.py index b75061992596..93094031b2e7 100644 --- a/mypyc/irbuild/builder.py +++ b/mypyc/irbuild/builder.py @@ -619,10 +619,8 @@ def load_literal_value(self, val: int | str | bytes | float | complex | bool | t elif isinstance(val, complex): return self.builder.load_complex(val) elif isinstance(val, tuple): - # TODO: validate this code path return self.builder.load_tuple(val) elif isinstance(val, dict): - # TODO: validate this code path return self.builder.load_dict(val) else: assert False, "Unsupported literal value" From 6954afc3e6d94c878428f1ec73e3fdce904acc2f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 30 Jul 2025 18:20:02 +0000 Subject: [PATCH 12/12] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypyc/irbuild/ll_builder.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index b1e05dd45c6a..f4da4a300c89 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -1282,7 +1282,9 @@ def load_complex(self, value: complex) -> Value: """Load a complex literal value.""" return self.add(LoadLiteral(value, object_rprimitive)) - def load_tuple(self, value: tuple[Any, ...]) -> Value: # should this be RTuple? conditional RTuple when length is known? + def load_tuple( + self, value: tuple[Any, ...] + ) -> Value: # should this be RTuple? conditional RTuple when length is known? return self.add(LoadLiteral(value, tuple_rprimitive)) def load_dict(self, value: dict[Any, Any]) -> Value: