| """Maintain a mapping from mypy concepts to IR/compiled concepts.""" |
| |
| from __future__ import annotations |
| |
| from mypy.nodes import ARG_STAR, ARG_STAR2, GDEF, ArgKind, FuncDef, RefExpr, SymbolNode, TypeInfo |
| from mypy.types import ( |
| AnyType, |
| CallableType, |
| Instance, |
| LiteralType, |
| NoneTyp, |
| Overloaded, |
| PartialType, |
| TupleType, |
| Type, |
| TypedDictType, |
| TypeType, |
| TypeVarType, |
| UnboundType, |
| UninhabitedType, |
| UnionType, |
| get_proper_type, |
| ) |
| from mypyc.ir.class_ir import ClassIR |
| from mypyc.ir.func_ir import FuncDecl, FuncSignature, RuntimeArg |
| from mypyc.ir.rtypes import ( |
| RInstance, |
| RTuple, |
| RType, |
| RUnion, |
| bool_rprimitive, |
| bytes_rprimitive, |
| dict_rprimitive, |
| float_rprimitive, |
| int32_rprimitive, |
| int64_rprimitive, |
| int_rprimitive, |
| list_rprimitive, |
| none_rprimitive, |
| object_rprimitive, |
| range_rprimitive, |
| set_rprimitive, |
| str_rprimitive, |
| tuple_rprimitive, |
| ) |
| |
| |
| class Mapper: |
| """Keep track of mappings from mypy concepts to IR concepts. |
| |
| For example, we keep track of how the mypy TypeInfos of compiled |
| classes map to class IR objects. |
| |
| This state is shared across all modules being compiled in all |
| compilation groups. |
| """ |
| |
| def __init__(self, group_map: dict[str, str | None]) -> None: |
| self.group_map = group_map |
| self.type_to_ir: dict[TypeInfo, ClassIR] = {} |
| self.func_to_decl: dict[SymbolNode, FuncDecl] = {} |
| |
| def type_to_rtype(self, typ: Type | None) -> RType: |
| if typ is None: |
| return object_rprimitive |
| |
| typ = get_proper_type(typ) |
| if isinstance(typ, Instance): |
| if typ.type.fullname == "builtins.int": |
| return int_rprimitive |
| elif typ.type.fullname == "builtins.float": |
| return float_rprimitive |
| elif typ.type.fullname == "builtins.bool": |
| return bool_rprimitive |
| elif typ.type.fullname == "builtins.str": |
| return str_rprimitive |
| elif typ.type.fullname == "builtins.bytes": |
| return bytes_rprimitive |
| elif typ.type.fullname == "builtins.list": |
| return list_rprimitive |
| # Dict subclasses are at least somewhat common and we |
| # specifically support them, so make sure that dict operations |
| # get optimized on them. |
| elif any(cls.fullname == "builtins.dict" for cls in typ.type.mro): |
| return dict_rprimitive |
| elif typ.type.fullname == "builtins.set": |
| return set_rprimitive |
| elif typ.type.fullname == "builtins.tuple": |
| return tuple_rprimitive # Varying-length tuple |
| elif typ.type.fullname == "builtins.range": |
| return range_rprimitive |
| elif typ.type in self.type_to_ir: |
| inst = RInstance(self.type_to_ir[typ.type]) |
| # Treat protocols as Union[protocol, object], so that we can do fast |
| # method calls in the cases where the protocol is explicitly inherited from |
| # and fall back to generic operations when it isn't. |
| if typ.type.is_protocol: |
| return RUnion([inst, object_rprimitive]) |
| else: |
| return inst |
| elif typ.type.fullname == "mypy_extensions.i64": |
| return int64_rprimitive |
| elif typ.type.fullname == "mypy_extensions.i32": |
| return int32_rprimitive |
| else: |
| return object_rprimitive |
| elif isinstance(typ, TupleType): |
| # Use our unboxed tuples for raw tuples but fall back to |
| # being boxed for NamedTuple. |
| if typ.partial_fallback.type.fullname == "builtins.tuple": |
| return RTuple([self.type_to_rtype(t) for t in typ.items]) |
| else: |
| return tuple_rprimitive |
| elif isinstance(typ, CallableType): |
| return object_rprimitive |
| elif isinstance(typ, NoneTyp): |
| return none_rprimitive |
| elif isinstance(typ, UnionType): |
| return RUnion([self.type_to_rtype(item) for item in typ.items]) |
| elif isinstance(typ, AnyType): |
| return object_rprimitive |
| elif isinstance(typ, TypeType): |
| return object_rprimitive |
| elif isinstance(typ, TypeVarType): |
| # Erase type variable to upper bound. |
| # TODO: Erase to union if object has value restriction? |
| return self.type_to_rtype(typ.upper_bound) |
| elif isinstance(typ, PartialType): |
| assert typ.var.type is not None |
| return self.type_to_rtype(typ.var.type) |
| elif isinstance(typ, Overloaded): |
| return object_rprimitive |
| elif isinstance(typ, TypedDictType): |
| return dict_rprimitive |
| elif isinstance(typ, LiteralType): |
| return self.type_to_rtype(typ.fallback) |
| elif isinstance(typ, (UninhabitedType, UnboundType)): |
| # Sure, whatever! |
| return object_rprimitive |
| |
| # I think we've covered everything that is supposed to |
| # actually show up, so anything else is a bug somewhere. |
| assert False, "unexpected type %s" % type(typ) |
| |
| def get_arg_rtype(self, typ: Type, kind: ArgKind) -> RType: |
| if kind == ARG_STAR: |
| return tuple_rprimitive |
| elif kind == ARG_STAR2: |
| return dict_rprimitive |
| else: |
| return self.type_to_rtype(typ) |
| |
| def fdef_to_sig(self, fdef: FuncDef) -> FuncSignature: |
| if isinstance(fdef.type, CallableType): |
| arg_types = [ |
| self.get_arg_rtype(typ, kind) |
| for typ, kind in zip(fdef.type.arg_types, fdef.type.arg_kinds) |
| ] |
| arg_pos_onlys = [name is None for name in fdef.type.arg_names] |
| ret = self.type_to_rtype(fdef.type.ret_type) |
| else: |
| # Handle unannotated functions |
| arg_types = [object_rprimitive for _ in fdef.arguments] |
| arg_pos_onlys = [arg.pos_only for arg in fdef.arguments] |
| # We at least know the return type for __init__ methods will be None. |
| is_init_method = fdef.name == "__init__" and bool(fdef.info) |
| if is_init_method: |
| ret = none_rprimitive |
| else: |
| ret = object_rprimitive |
| |
| # mypyc FuncSignatures (unlike mypy types) want to have a name |
| # present even when the argument is position only, since it is |
| # the sole way that FuncDecl arguments are tracked. This is |
| # generally fine except in some cases (like for computing |
| # init_sig) we need to produce FuncSignatures from a |
| # deserialized FuncDef that lacks arguments. We won't ever |
| # need to use those inside of a FuncIR, so we just make up |
| # some crap. |
| if hasattr(fdef, "arguments"): |
| arg_names = [arg.variable.name for arg in fdef.arguments] |
| else: |
| arg_names = [name or "" for name in fdef.arg_names] |
| |
| args = [ |
| RuntimeArg(arg_name, arg_type, arg_kind, arg_pos_only) |
| for arg_name, arg_kind, arg_type, arg_pos_only in zip( |
| arg_names, fdef.arg_kinds, arg_types, arg_pos_onlys |
| ) |
| ] |
| |
| # We force certain dunder methods to return objects to support letting them |
| # return NotImplemented. It also avoids some pointless boxing and unboxing, |
| # since tp_richcompare needs an object anyways. |
| if fdef.name in ("__eq__", "__ne__", "__lt__", "__gt__", "__le__", "__ge__"): |
| ret = object_rprimitive |
| return FuncSignature(args, ret) |
| |
| def is_native_module(self, module: str) -> bool: |
| """Is the given module one compiled by mypyc?""" |
| return module in self.group_map |
| |
| def is_native_ref_expr(self, expr: RefExpr) -> bool: |
| if expr.node is None: |
| return False |
| if "." in expr.node.fullname: |
| return self.is_native_module(expr.node.fullname.rpartition(".")[0]) |
| return True |
| |
| def is_native_module_ref_expr(self, expr: RefExpr) -> bool: |
| return self.is_native_ref_expr(expr) and expr.kind == GDEF |