| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- """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,
- int16_rprimitive,
- int32_rprimitive,
- int64_rprimitive,
- int_rprimitive,
- list_rprimitive,
- none_rprimitive,
- object_rprimitive,
- range_rprimitive,
- set_rprimitive,
- str_rprimitive,
- tuple_rprimitive,
- uint8_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
- elif typ.type.fullname == "mypy_extensions.i16":
- return int16_rprimitive
- elif typ.type.fullname == "mypy_extensions.u8":
- return uint8_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.make_simplified_union([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
|