# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import annotations import operator from dataclasses import dataclass from typing import TYPE_CHECKING, Any from .utils import hashable if TYPE_CHECKING: from collections.abc import Callable BinaryOp = Callable[[Any, Any], Any] UnaryOp = Callable[[Any], Any] INPLACE_BINARY_OPS_TO_MAGIC_NAMES: dict[BinaryOp, tuple[str, BinaryOp]] = { # inplace op fn: (magic name, non-inplace op fn) operator.iadd: ("__iadd__", operator.add), operator.iand: ("__iand__", operator.and_), operator.iconcat: ("__iconcat__", operator.concat), operator.ifloordiv: ("__ifloordiv__", operator.floordiv), operator.ilshift: ("__ilshift__", operator.lshift), operator.imatmul: ("__imatmul__", operator.matmul), operator.imod: ("__imod__", operator.mod), operator.imul: ("__imul__", operator.mul), operator.ior: ("__ior__", operator.or_), operator.ipow: ("__ipow__", operator.pow), operator.irshift: ("__irshift__", operator.rshift), operator.isub: ("__isub__", operator.sub), operator.itruediv: ("__itruediv__", operator.truediv), operator.ixor: ("__ixor__", operator.xor), } NON_INPLACE_BINARY_OPS_TO_MAGIC_NAMES: dict[ BinaryOp, tuple[str, str | None] ] = { # op fn: (magic name, reverse magic name) operator.add: ("__add__", "__radd__"), operator.and_: ("__and__", "__rand__"), operator.contains: ("__contains__", None), operator.delitem: ("__delitem__", None), operator.eq: ("__eq__", "__eq__"), operator.floordiv: ("__floordiv__", "__rfloordiv__"), operator.ge: ("__ge__", "__le__"), operator.getitem: ("__getitem__", None), operator.gt: ("__gt__", "__lt__"), operator.le: ("__le__", "__ge__"), operator.lshift: ("__lshift__", "__rlshift__"), operator.lt: ("__lt__", "__gt__"), operator.matmul: ("__matmul__", "__rmatmul__"), operator.mod: ("__mod__", "__rmod__"), operator.mul: ("__mul__", "__rmul__"), operator.ne: ("__ne__", "__ne__"), operator.or_: ("__or__", "__ror__"), operator.pow: ("__pow__", "__rpow__"), operator.rshift: ("__rshift__", "__rrshift__"), operator.sub: ("__sub__", "__rsub__"), operator.truediv: ("__truediv__", "__rtruediv__"), operator.xor: ("__xor__", "__rxor__"), } UNARY_OPS_TO_MAGIC_NAMES: dict[UnaryOp, str] = { operator.neg: "__neg__", operator.invert: "__invert__", operator.pos: "__pos__", operator.abs: "__abs__", operator.index: "__index__", operator.inv: "__inv__", operator.truth: "__bool__", bool: "__bool__", abs: "__abs__", float: "__float__", len: "__len__", int: "__int__", complex: "__complex__", } # TODO(SigureMo): support any, all, sum INPLACE_BINARY_OPS = set(INPLACE_BINARY_OPS_TO_MAGIC_NAMES.keys()) NON_INPLACE_BINARY_OPS = set(NON_INPLACE_BINARY_OPS_TO_MAGIC_NAMES.keys()) BINARY_OPS = INPLACE_BINARY_OPS | NON_INPLACE_BINARY_OPS UNARY_OPS = set(UNARY_OPS_TO_MAGIC_NAMES.keys()) # NOTE: Both operator.pow and operator.ipow should be considered for inclusion in this list, # as they raise ZeroDivisionError when evaluating 0^n where n < 0 (division by zero). NEED_GUARD_ZERO_DIVISION_ERROR_OPS: list[BinaryOp] = [ operator.floordiv, operator.truediv, operator.mod, operator.ifloordiv, operator.itruediv, operator.imod, ] @dataclass class MagicMethod: name: str is_inplace: bool = False is_reverse: bool = False def magic_method_builtin_dispatch(fn: BinaryOp | UnaryOp) -> list[MagicMethod]: if not hashable(fn): return [] if fn in INPLACE_BINARY_OPS: inplace_magic_name, non_inplace_op = INPLACE_BINARY_OPS_TO_MAGIC_NAMES[ fn ] return [ MagicMethod(inplace_magic_name, is_inplace=True), *magic_method_builtin_dispatch(non_inplace_op), ] elif fn in NON_INPLACE_BINARY_OPS: magic_name, reverse_magic_name = NON_INPLACE_BINARY_OPS_TO_MAGIC_NAMES[ fn ] magic_methods = [MagicMethod(magic_name)] if reverse_magic_name is not None: magic_methods.append( MagicMethod(reverse_magic_name, is_reverse=True) ) return magic_methods elif fn in UNARY_OPS: magic_name = UNARY_OPS_TO_MAGIC_NAMES[fn] return [MagicMethod(magic_name)] return [] def non_inplace_op_to_inplace_op( fn: BinaryOp, ) -> BinaryOp | None: for inplace_op, ( _, non_inplace_op, ) in INPLACE_BINARY_OPS_TO_MAGIC_NAMES.items(): if fn is non_inplace_op: return inplace_op return None