From 665f5ad642859435157c96fcae2d50255c9a57f9 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sun, 17 Aug 2025 00:58:41 -0700 Subject: [PATCH 1/7] [mypy] perf --- mypy/types.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/mypy/types.py b/mypy/types.py index 26c5b474ba6c..9bca33b27c6b 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -1471,7 +1471,7 @@ def accept(self, visitor: TypeVisitor[T]) -> T: def __hash__(self) -> int: if self._hash == -1: - self._hash = hash((self.type, self.args, self.last_known_value, self.extra_attrs)) + self._hash = hash((self.type, self.args, self.last_known_value)) return self._hash def __eq__(self, other: object) -> bool: @@ -2895,6 +2895,7 @@ class UnionType(ProperType): __slots__ = ( "items", + "_frozen_items", "is_evaluated", "uses_pep604_syntax", "original_str_expr", @@ -2914,6 +2915,7 @@ def __init__( # We must keep this false to avoid crashes during semantic analysis. # TODO: maybe switch this to True during type-checking pass? self.items = flatten_nested_unions(items, handle_type_alias_type=False) + self._frozen_items = frozenset(self.items) # is_evaluated should be set to false for type comments and string literals self.is_evaluated = is_evaluated # uses_pep604_syntax is True if Union uses OR syntax (X | Y) @@ -2931,14 +2933,14 @@ def can_be_false_default(self) -> bool: return any(item.can_be_false for item in self.items) def __hash__(self) -> int: - return hash(frozenset(self.items)) + return hash(self._frozen_items) def __eq__(self, other: object) -> bool: if not isinstance(other, UnionType): return NotImplemented if self is other: return True - return frozenset(self.items) == frozenset(other.items) + return self._frozen_items == other._frozen_items @overload @staticmethod From 93b4a3a27842456efcee332a01a8c6228608ce1e Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sun, 17 Aug 2025 03:32:10 -0700 Subject: [PATCH 2/7] [mypy] contextmanager --- mypyc/irbuild/statement.py | 111 ++++++++++++++++++++++++++++++++++++- 1 file changed, 110 insertions(+), 1 deletion(-) diff --git a/mypyc/irbuild/statement.py b/mypyc/irbuild/statement.py index eeeb40ac672f..6337bc86278d 100644 --- a/mypyc/irbuild/statement.py +++ b/mypyc/irbuild/statement.py @@ -111,7 +111,7 @@ reraise_exception_op, restore_exc_info_op, ) -from mypyc.primitives.generic_ops import iter_op, next_raw_op, py_delattr_op +from mypyc.primitives.generic_ops import iter_op, next_op, next_raw_op, py_delattr_op from mypyc.primitives.misc_ops import ( check_stop_op, coro_op, @@ -887,6 +887,19 @@ def transform_with( is_async: bool, line: int, ) -> None: + + if ( + not is_async + and isinstance(expr, mypy.nodes.CallExpr) + and isinstance(expr.callee, mypy.nodes.RefExpr) + and isinstance(dec := expr.callee.node, mypy.nodes.Decorator) + and len(dec.decorators) == 1 + and isinstance(dec1 := dec.decorators[0], mypy.nodes.RefExpr) + and dec1.node + and dec1.node.fullname == "contextlib.contextmanager" + ): + return _transform_with_contextmanager(builder, expr, target, body, line) + # This is basically a straight transcription of the Python code in PEP 343. # I don't actually understand why a bunch of it is the way it is. # We could probably optimize the case where the manager is compiled by us, @@ -964,6 +977,102 @@ def finally_body() -> None: ) +def _transform_with_contextmanager( + builder: IRBuilder, + expr: mypy.nodes.CallExpr, + target: Lvalue | None, + with_body: GenFunc, + line: int, +) -> None: + assert isinstance(expr.callee, mypy.nodes.RefExpr) + dec = expr.callee.node + assert isinstance(dec, mypy.nodes.Decorator) + + # mgrv = ctx.__wrapped__(*args, **kwargs) + wrapped_call = mypy.nodes.CallExpr( + mypy.nodes.MemberExpr(expr.callee, "__wrapped__"), expr.args, expr.arg_kinds, expr.arg_names + ) + gen = builder.accept(wrapped_call) + + # try: + # target = next(gen) + # except StopIteration: + # raise RuntimeError("generator didn't yield") from None + mgr_target = builder.call_c(next_raw_op, [gen], line) + + runtime_block, main_block = BasicBlock(), BasicBlock() + builder.add(Branch(mgr_target, runtime_block, main_block, Branch.IS_ERROR)) + + builder.activate_block(runtime_block) + builder.add(RaiseStandardError(RaiseStandardError.RUNTIME_ERROR, "generator didn't yield", line)) + builder.add(Unreachable()) + + builder.activate_block(main_block) + + # try: + # {body} + + def try_body() -> None: + if target: + builder.assign(builder.get_assignment_target(target), mgr_target, line) + with_body() + + # except Exception as e: + # exc = True + # try: + # gen.throw(e) + # except StopIteration as e2: + # if e2 is not e: + # raise + # return + # except RuntimeError: + # # TODO: some other stuff + # ... + # except BaseException: + # # TODO: some other stuff + # ... + + def except_body() -> None: + builder.add(RaiseStandardError(RaiseStandardError.RUNTIME_ERROR, "TODO", line)) + builder.add(Unreachable()) + + # TODO: actually do the exceptions + handlers = [(None, None, except_body)] + + # else: + # try: + # next(gen) + # except StopIteration: + # pass + # else: + # try: + # raise RuntimeError("generator didn't stop") + # finally: + # gen.close() + + def else_body() -> None: + value = builder.call_c(next_raw_op, [builder.read(gen)], line) + stop_block, close_block = BasicBlock(), BasicBlock() + builder.add(Branch(value, stop_block, close_block, Branch.IS_ERROR)) + + builder.activate_block(close_block) + # TODO: this isn't exactly the right order + builder.py_call(builder.py_get_attr(gen, "close", line), [], line) + builder.add(RaiseStandardError(RaiseStandardError.RUNTIME_ERROR, "generator didn't stop", line)) + builder.add(Unreachable()) + + builder.activate_block(stop_block) + builder.call_c(error_catch_op, [], -1) + + transform_try_except( + builder, + try_body, + handlers, + else_body, + line, + ) + + def transform_with_stmt(builder: IRBuilder, o: WithStmt) -> None: # Generate separate logic for each expr in it, left to right def generate(i: int) -> None: From 1dfbcb4e3a4e68e9643f404ab1cb427cb15de709 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sun, 17 Aug 2025 04:02:43 -0700 Subject: [PATCH 3/7] more ctx --- mypyc/irbuild/statement.py | 40 ++++++++++++++++++++++++++++++------- mypyc/primitives/exc_ops.py | 5 +++++ 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/mypyc/irbuild/statement.py b/mypyc/irbuild/statement.py index 6337bc86278d..35550c9e6f96 100644 --- a/mypyc/irbuild/statement.py +++ b/mypyc/irbuild/statement.py @@ -105,11 +105,13 @@ get_exc_info_op, get_exc_value_op, keep_propagating_op, + err_occurred_op, no_err_occurred_op, propagate_if_error_op, raise_exception_op, reraise_exception_op, restore_exc_info_op, + error_clear_op, ) from mypyc.primitives.generic_ops import iter_op, next_op, next_raw_op, py_delattr_op from mypyc.primitives.misc_ops import ( @@ -1018,7 +1020,6 @@ def try_body() -> None: with_body() # except Exception as e: - # exc = True # try: # gen.throw(e) # except StopIteration as e2: @@ -1026,16 +1027,40 @@ def try_body() -> None: # raise # return # except RuntimeError: - # # TODO: some other stuff - # ... + # # TODO: check the traceback munging + # raise # except BaseException: - # # TODO: some other stuff - # ... + # # approximately + # raise def except_body() -> None: - builder.add(RaiseStandardError(RaiseStandardError.RUNTIME_ERROR, "TODO", line)) + exc_original = builder.call_c(get_exc_value_op, [], line) + + builder.py_call(builder.py_get_attr(gen, "throw", line), [], line) + err_occurred = builder.call_c(err_occurred_op, [], line) + + error_block, no_error_block = BasicBlock(), BasicBlock() + builder.add(Branch(err_occurred, error_block, no_error_block, Branch.BOOL)) + + builder.activate_block(error_block) + + stop_iteration = builder.call_c(check_stop_op, [], line) + is_same_exc = builder.binary_op(stop_iteration, exc_original, "is", line) + + suppress_block, propagate_block = BasicBlock(), BasicBlock() + builder.add(Branch(is_same_exc, suppress_block, propagate_block, Branch.BOOL)) + + builder.activate_block(propagate_block) + builder.call_c(keep_propagating_op, [], line) + builder.add(Unreachable()) + + builder.activate_block(no_error_block) + builder.add(RaiseStandardError(RaiseStandardError.RUNTIME_ERROR, "generator didn't stop after throw()", line)) builder.add(Unreachable()) + builder.activate_block(suppress_block) + builder.call_c(error_clear_op, [], -1) + # TODO: actually do the exceptions handlers = [(None, None, except_body)] @@ -1062,7 +1087,8 @@ def else_body() -> None: builder.add(Unreachable()) builder.activate_block(stop_block) - builder.call_c(error_catch_op, [], -1) + # TODO: should check for StopIteration + builder.call_c(error_clear_op, [], -1) transform_try_except( builder, diff --git a/mypyc/primitives/exc_ops.py b/mypyc/primitives/exc_ops.py index e1234f807afa..01258712d3ad 100644 --- a/mypyc/primitives/exc_ops.py +++ b/mypyc/primitives/exc_ops.py @@ -80,6 +80,11 @@ arg_types=[], return_type=exc_rtuple, c_function_name="CPy_CatchError", error_kind=ERR_NEVER ) +# Clear the current exception. +error_clear_op = custom_op( + arg_types=[], return_type=void_rtype, c_function_name="PyErr_Clear", error_kind=ERR_NEVER +) + # Restore an old "currently handled exception" returned from. # error_catch (by sticking it into sys.exc_info()) restore_exc_info_op = custom_op( From 1ab1f165c95f87a291ea5987b2447c0f0bfc6d5b Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sun, 17 Aug 2025 05:50:13 -0700 Subject: [PATCH 4/7] even more context --- mypyc/irbuild/statement.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/mypyc/irbuild/statement.py b/mypyc/irbuild/statement.py index 35550c9e6f96..79cc11dbc30d 100644 --- a/mypyc/irbuild/statement.py +++ b/mypyc/irbuild/statement.py @@ -117,6 +117,7 @@ from mypyc.primitives.misc_ops import ( check_stop_op, coro_op, + debug_print_op, import_from_many_op, import_many_op, import_op, @@ -1036,16 +1037,21 @@ def try_body() -> None: def except_body() -> None: exc_original = builder.call_c(get_exc_value_op, [], line) - builder.py_call(builder.py_get_attr(gen, "throw", line), [], line) - err_occurred = builder.call_c(err_occurred_op, [], line) - error_block, no_error_block = BasicBlock(), BasicBlock() - builder.add(Branch(err_occurred, error_block, no_error_block, Branch.BOOL)) - builder.activate_block(error_block) + builder.builder.push_error_handler(error_block) + builder.goto_and_activate(BasicBlock()) + builder.py_call(builder.py_get_attr(gen, "throw", line), [exc_original], line) + builder.goto(no_error_block) + builder.builder.pop_error_handler() + + builder.activate_block(no_error_block) + builder.add(RaiseStandardError(RaiseStandardError.RUNTIME_ERROR, "generator didn't stop after throw()", line)) + builder.add(Unreachable()) + builder.activate_block(error_block) stop_iteration = builder.call_c(check_stop_op, [], line) - is_same_exc = builder.binary_op(stop_iteration, exc_original, "is", line) + is_same_exc = builder.binary_op(stop_iteration, exc_original, "==", line) suppress_block, propagate_block = BasicBlock(), BasicBlock() builder.add(Branch(is_same_exc, suppress_block, propagate_block, Branch.BOOL)) @@ -1054,10 +1060,6 @@ def except_body() -> None: builder.call_c(keep_propagating_op, [], line) builder.add(Unreachable()) - builder.activate_block(no_error_block) - builder.add(RaiseStandardError(RaiseStandardError.RUNTIME_ERROR, "generator didn't stop after throw()", line)) - builder.add(Unreachable()) - builder.activate_block(suppress_block) builder.call_c(error_clear_op, [], -1) From e1d5197d8a2b781c90eeb09758ff5457724c6f5f Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sun, 17 Aug 2025 12:28:21 -0700 Subject: [PATCH 5/7] . --- mypyc/irbuild/statement.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mypyc/irbuild/statement.py b/mypyc/irbuild/statement.py index 79cc11dbc30d..73ea91558ed5 100644 --- a/mypyc/irbuild/statement.py +++ b/mypyc/irbuild/statement.py @@ -995,6 +995,7 @@ def _transform_with_contextmanager( wrapped_call = mypy.nodes.CallExpr( mypy.nodes.MemberExpr(expr.callee, "__wrapped__"), expr.args, expr.arg_kinds, expr.arg_names ) + wrapped_call.line = line gen = builder.accept(wrapped_call) # try: From 273240aee40e48cf4a89f33f8164459b01b2c0ef Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sun, 17 Aug 2025 12:28:29 -0700 Subject: [PATCH 6/7] Revert "[mypy] perf" This reverts commit 665f5ad642859435157c96fcae2d50255c9a57f9. --- mypy/types.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/mypy/types.py b/mypy/types.py index 9bca33b27c6b..26c5b474ba6c 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -1471,7 +1471,7 @@ def accept(self, visitor: TypeVisitor[T]) -> T: def __hash__(self) -> int: if self._hash == -1: - self._hash = hash((self.type, self.args, self.last_known_value)) + self._hash = hash((self.type, self.args, self.last_known_value, self.extra_attrs)) return self._hash def __eq__(self, other: object) -> bool: @@ -2895,7 +2895,6 @@ class UnionType(ProperType): __slots__ = ( "items", - "_frozen_items", "is_evaluated", "uses_pep604_syntax", "original_str_expr", @@ -2915,7 +2914,6 @@ def __init__( # We must keep this false to avoid crashes during semantic analysis. # TODO: maybe switch this to True during type-checking pass? self.items = flatten_nested_unions(items, handle_type_alias_type=False) - self._frozen_items = frozenset(self.items) # is_evaluated should be set to false for type comments and string literals self.is_evaluated = is_evaluated # uses_pep604_syntax is True if Union uses OR syntax (X | Y) @@ -2933,14 +2931,14 @@ def can_be_false_default(self) -> bool: return any(item.can_be_false for item in self.items) def __hash__(self) -> int: - return hash(self._frozen_items) + return hash(frozenset(self.items)) def __eq__(self, other: object) -> bool: if not isinstance(other, UnionType): return NotImplemented if self is other: return True - return self._frozen_items == other._frozen_items + return frozenset(self.items) == frozenset(other.items) @overload @staticmethod From 13f5d3a7522d4516e018bcf62a8fac2e19c0d25a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 17 Aug 2025 19:29:54 +0000 Subject: [PATCH 7/7] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypyc/irbuild/statement.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/mypyc/irbuild/statement.py b/mypyc/irbuild/statement.py index 73ea91558ed5..9066ed1deeac 100644 --- a/mypyc/irbuild/statement.py +++ b/mypyc/irbuild/statement.py @@ -101,23 +101,21 @@ ) from mypyc.primitives.exc_ops import ( error_catch_op, + error_clear_op, exc_matches_op, get_exc_info_op, get_exc_value_op, keep_propagating_op, - err_occurred_op, no_err_occurred_op, propagate_if_error_op, raise_exception_op, reraise_exception_op, restore_exc_info_op, - error_clear_op, ) -from mypyc.primitives.generic_ops import iter_op, next_op, next_raw_op, py_delattr_op +from mypyc.primitives.generic_ops import iter_op, next_raw_op, py_delattr_op from mypyc.primitives.misc_ops import ( check_stop_op, coro_op, - debug_print_op, import_from_many_op, import_many_op, import_op, @@ -993,7 +991,10 @@ def _transform_with_contextmanager( # mgrv = ctx.__wrapped__(*args, **kwargs) wrapped_call = mypy.nodes.CallExpr( - mypy.nodes.MemberExpr(expr.callee, "__wrapped__"), expr.args, expr.arg_kinds, expr.arg_names + mypy.nodes.MemberExpr(expr.callee, "__wrapped__"), + expr.args, + expr.arg_kinds, + expr.arg_names, ) wrapped_call.line = line gen = builder.accept(wrapped_call) @@ -1008,7 +1009,9 @@ def _transform_with_contextmanager( builder.add(Branch(mgr_target, runtime_block, main_block, Branch.IS_ERROR)) builder.activate_block(runtime_block) - builder.add(RaiseStandardError(RaiseStandardError.RUNTIME_ERROR, "generator didn't yield", line)) + builder.add( + RaiseStandardError(RaiseStandardError.RUNTIME_ERROR, "generator didn't yield", line) + ) builder.add(Unreachable()) builder.activate_block(main_block) @@ -1047,7 +1050,11 @@ def except_body() -> None: builder.builder.pop_error_handler() builder.activate_block(no_error_block) - builder.add(RaiseStandardError(RaiseStandardError.RUNTIME_ERROR, "generator didn't stop after throw()", line)) + builder.add( + RaiseStandardError( + RaiseStandardError.RUNTIME_ERROR, "generator didn't stop after throw()", line + ) + ) builder.add(Unreachable()) builder.activate_block(error_block) @@ -1086,20 +1093,16 @@ def else_body() -> None: builder.activate_block(close_block) # TODO: this isn't exactly the right order builder.py_call(builder.py_get_attr(gen, "close", line), [], line) - builder.add(RaiseStandardError(RaiseStandardError.RUNTIME_ERROR, "generator didn't stop", line)) + builder.add( + RaiseStandardError(RaiseStandardError.RUNTIME_ERROR, "generator didn't stop", line) + ) builder.add(Unreachable()) builder.activate_block(stop_block) # TODO: should check for StopIteration builder.call_c(error_clear_op, [], -1) - transform_try_except( - builder, - try_body, - handlers, - else_body, - line, - ) + transform_try_except(builder, try_body, handlers, else_body, line) def transform_with_stmt(builder: IRBuilder, o: WithStmt) -> None: