def setup_env_class(builder: IRBuilder) -> ClassIR: """Generate a class representing a function environment. Note that the variables in the function environment are not actually populated here. This is because when the environment class is generated, the function environment has not yet been visited. This behavior is allowed so that when the compiler visits nested functions, it can use the returned ClassIR instance to figure out free variables it needs to access. The remaining attributes of the environment class are populated when the environment registers are loaded. Return a ClassIR representing an environment for a function containing a nested function. """ env_class = ClassIR('{}_env'.format(builder.fn_info.namespaced_name()), builder.module_name, is_generated=True) env_class.attributes[SELF_NAME] = RInstance(env_class) if builder.fn_info.is_nested: # If the function is nested, its environment class must contain an environment # attribute pointing to its encapsulating functions' environment class. env_class.attributes[ENV_ATTR_NAME] = RInstance( builder.fn_infos[-2].env_class) env_class.mro = [env_class] builder.fn_info.env_class = env_class builder.classes.append(env_class) return env_class
def enter_method(self, class_ir: ClassIR, name: str, ret_type: RType, fn_info: Union[FuncInfo, str] = '', self_type: Optional[RType] = None) -> None: """Begin generating IR for a method. If the method takes arguments, you should immediately afterwards call add_argument() for each non-self argument (self is created implicitly). Call leave_method() to finish the generation of the method. You can enter multiple methods at a time. They are maintained in a stack, and leave_method() leaves the topmost one. Args: class_ir: Add method to this class name: Short name of the method ret_type: Return type of the method fn_info: Optionally, additional information about the method self_type: If not None, override default type of the implicit 'self' argument (by default, derive type from class_ir) """ self.enter(fn_info) self.function_name_stack.append(name) self.class_ir_stack.append(class_ir) self.ret_types[-1] = ret_type if self_type is None: self_type = RInstance(class_ir) self.add_argument(SELF_NAME, self_type)
def gen_glue_property(builder: IRBuilder, sig: FuncSignature, target: FuncIR, cls: ClassIR, base: ClassIR, line: int, do_pygetattr: bool) -> FuncIR: """Generate glue methods for properties that mediate between different subclass types. Similarly to methods, properties of derived types can be covariantly subtyped. Thus, properties also require glue. However, this only requires the return type to change. Further, instead of a method call, an attribute get is performed. If do_pygetattr is True, then get the attribute using the Python C API instead of a native call. """ builder.enter() rt_arg = RuntimeArg(SELF_NAME, RInstance(cls)) arg = builder.read(add_self_to_env(builder.environment, cls), line) builder.ret_types[-1] = sig.ret_type if do_pygetattr: retval = builder.py_get_attr(arg, target.name, line) else: retval = builder.add(GetAttr(arg, target.name, line)) retbox = builder.coerce(retval, sig.ret_type, line) builder.add(Return(retbox)) blocks, env, return_type, _ = builder.leave() return FuncIR( FuncDecl(target.name + '__' + base.name + '_glue', cls.name, builder.module_name, FuncSignature([rt_arg], return_type)), blocks, env)
def setUp(self) -> None: self.env = Environment() self.n = self.env.add_local(Var('n'), int_rprimitive) self.m = self.env.add_local(Var('m'), int_rprimitive) self.k = self.env.add_local(Var('k'), int_rprimitive) self.l = self.env.add_local(Var('l'), list_rprimitive) # noqa self.ll = self.env.add_local(Var('ll'), list_rprimitive) self.o = self.env.add_local(Var('o'), object_rprimitive) self.o2 = self.env.add_local(Var('o2'), object_rprimitive) self.d = self.env.add_local(Var('d'), dict_rprimitive) self.b = self.env.add_local(Var('b'), bool_rprimitive) self.s1 = self.env.add_local(Var('s1'), short_int_rprimitive) self.s2 = self.env.add_local(Var('s2'), short_int_rprimitive) self.i32 = self.env.add_local(Var('i32'), int32_rprimitive) self.i32_1 = self.env.add_local(Var('i32_1'), int32_rprimitive) self.i64 = self.env.add_local(Var('i64'), int64_rprimitive) self.i64_1 = self.env.add_local(Var('i64_1'), int64_rprimitive) self.ptr = self.env.add_local(Var('ptr'), pointer_rprimitive) self.t = self.env.add_local(Var('t'), RTuple([int_rprimitive, bool_rprimitive])) self.tt = self.env.add_local( Var('tt'), RTuple( [RTuple([int_rprimitive, bool_rprimitive]), bool_rprimitive])) ir = ClassIR('A', 'mod') ir.attributes = OrderedDict([('x', bool_rprimitive), ('y', int_rprimitive)]) compute_vtable(ir) ir.mro = [ir] self.r = self.env.add_local(Var('r'), RInstance(ir)) self.context = EmitterContext(NameGenerator([['mod']]))
def setUp(self) -> None: self.env = Environment() self.n = self.env.add_local(Var('n'), int_rprimitive) self.m = self.env.add_local(Var('m'), int_rprimitive) self.k = self.env.add_local(Var('k'), int_rprimitive) self.l = self.env.add_local(Var('l'), list_rprimitive) # noqa self.ll = self.env.add_local(Var('ll'), list_rprimitive) self.o = self.env.add_local(Var('o'), object_rprimitive) self.o2 = self.env.add_local(Var('o2'), object_rprimitive) self.d = self.env.add_local(Var('d'), dict_rprimitive) self.b = self.env.add_local(Var('b'), bool_rprimitive) self.t = self.env.add_local(Var('t'), RTuple([int_rprimitive, bool_rprimitive])) self.tt = self.env.add_local( Var('tt'), RTuple( [RTuple([int_rprimitive, bool_rprimitive]), bool_rprimitive])) ir = ClassIR('A', 'mod') ir.attributes = OrderedDict([('x', bool_rprimitive), ('y', int_rprimitive)]) compute_vtable(ir) ir.mro = [ir] self.r = self.env.add_local(Var('r'), RInstance(ir)) self.context = EmitterContext(NameGenerator([['mod']])) self.emitter = Emitter(self.context, self.env) self.declarations = Emitter(self.context, self.env) self.visitor = FunctionEmitterVisitor(self.emitter, self.declarations, 'prog.py', 'prog')
def gen_glue_method(builder: IRBuilder, sig: FuncSignature, target: FuncIR, cls: ClassIR, base: ClassIR, line: int, do_pycall: bool, ) -> FuncIR: """Generate glue methods that mediate between different method types in subclasses. For example, if we have: class A: def f(builder: IRBuilder, x: int) -> object: ... then it is totally permissible to have a subclass class B(A): def f(builder: IRBuilder, x: object) -> int: ... since '(object) -> int' is a subtype of '(int) -> object' by the usual contra/co-variant function subtyping rules. The trickiness here is that int and object have different runtime representations in mypyc, so A.f and B.f have different signatures at the native C level. To deal with this, we need to generate glue methods that mediate between the different versions by coercing the arguments and return values. If do_pycall is True, then make the call using the C API instead of a native call. """ builder.enter() builder.ret_types[-1] = sig.ret_type rt_args = list(sig.args) if target.decl.kind == FUNC_NORMAL: rt_args[0] = RuntimeArg(sig.args[0].name, RInstance(cls)) # The environment operates on Vars, so we make some up fake_vars = [(Var(arg.name), arg.type) for arg in rt_args] args = [builder.read(builder.add_local_reg(var, type, is_arg=True), line) for var, type in fake_vars] arg_names = [arg.name if arg.kind in (ARG_NAMED, ARG_NAMED_OPT) else None for arg in rt_args] arg_kinds = [concrete_arg_kind(arg.kind) for arg in rt_args] if do_pycall: retval = builder.builder.py_method_call( args[0], target.name, args[1:], line, arg_kinds[1:], arg_names[1:]) else: retval = builder.builder.call(target.decl, args, arg_kinds, arg_names, line) retval = builder.coerce(retval, sig.ret_type, line) builder.add(Return(retval)) arg_regs, _, blocks, ret_type, _ = builder.leave() return FuncIR( FuncDecl(target.name + '__' + base.name + '_glue', cls.name, builder.module_name, FuncSignature(rt_args, ret_type), target.decl.kind), arg_regs, blocks)
def setup_generator_class(builder: IRBuilder) -> ClassIR: name = '{}_gen'.format(builder.fn_info.namespaced_name()) generator_class_ir = ClassIR(name, builder.module_name, is_generated=True) generator_class_ir.attributes[ENV_ATTR_NAME] = RInstance(builder.fn_info.env_class) generator_class_ir.mro = [generator_class_ir] builder.classes.append(generator_class_ir) builder.fn_info.generator_class = GeneratorClass(generator_class_ir) return generator_class_ir
def setup_callable_class(builder: IRBuilder) -> None: """Generates a callable class representing a nested function or a function within a non-extension class and sets up the 'self' variable for that class. This takes the most recently visited function and returns a ClassIR to represent that function. Each callable class contains an environment attribute with points to another ClassIR representing the environment class where some of its variables can be accessed. Note that its '__call__' method is not yet implemented, and is implemented in the add_call_to_callable_class function. Returns a newly constructed ClassIR representing the callable class for the nested function. """ # Check to see that the name has not already been taken. If so, rename the class. We allow # multiple uses of the same function name because this is valid in if-else blocks. Example: # if True: # def foo(): ----> foo_obj() # return True # else: # def foo(): ----> foo_obj_0() # return False name = base_name = '{}_obj'.format(builder.fn_info.namespaced_name()) count = 0 while name in builder.callable_class_names: name = base_name + '_' + str(count) count += 1 builder.callable_class_names.add(name) # Define the actual callable class ClassIR, and set its environment to point at the # previously defined environment class. callable_class_ir = ClassIR(name, builder.module_name, is_generated=True) # The functools @wraps decorator attempts to call setattr on nested functions, so # we create a dict for these nested functions. # https://github.com/python/cpython/blob/3.7/Lib/functools.py#L58 if builder.fn_info.is_nested: callable_class_ir.has_dict = True # If the enclosing class doesn't contain nested (which will happen if # this is a toplevel lambda), don't set up an environment. if builder.fn_infos[-2].contains_nested: callable_class_ir.attributes[ENV_ATTR_NAME] = RInstance( builder.fn_infos[-2].env_class ) callable_class_ir.mro = [callable_class_ir] builder.fn_info.callable_class = ImplicitClass(callable_class_ir) builder.classes.append(callable_class_ir) # Add a 'self' variable to the callable class' environment, and store that variable in a # register to be accessed later. self_target = add_self_to_env(builder.environment, callable_class_ir) builder.fn_info.callable_class.self_reg = builder.read(self_target, builder.fn_info.fitem.line)
def test_can_coerce_to(self) -> None: cls = ClassIR(name="Cls", module_name="cls") valid_cases = [ (int64_rprimitive, int64_rprimitive), (str_rprimitive, str_rprimitive), (str_rprimitive, object_rprimitive), (object_rprimitive, str_rprimitive), (RUnion([bytes_rprimitive, str_rprimitive]), str_rprimitive), (str_rprimitive, RUnion([bytes_rprimitive, str_rprimitive])), (RInstance(cls), object_rprimitive), ] invalid_cases = [ (int64_rprimitive, int32_rprimitive), (RInstance(cls), str_rprimitive), (str_rprimitive, bytes_rprimitive), ] for src, dest in valid_cases: assert can_coerce_to(src, dest) for src, dest in invalid_cases: assert not can_coerce_to(src, dest)
def gen_glue_ne_method(builder: IRBuilder, cls: ClassIR, line: int) -> FuncIR: """Generate a "__ne__" method from a "__eq__" method. """ builder.enter() rt_args = (RuntimeArg("self", RInstance(cls)), RuntimeArg("rhs", object_rprimitive)) # The environment operates on Vars, so we make some up fake_vars = [(Var(arg.name), arg.type) for arg in rt_args] args = [ builder.read( builder.environment.add_local_reg( var, type, is_arg=True ), line ) for var, type in fake_vars ] # type: List[Value] builder.ret_types[-1] = object_rprimitive # If __eq__ returns NotImplemented, then __ne__ should also not_implemented_block, regular_block = BasicBlock(), BasicBlock() eqval = builder.add(MethodCall(args[0], '__eq__', [args[1]], line)) not_implemented = builder.add(LoadAddress(not_implemented_op.type, not_implemented_op.src, line)) builder.add(Branch( builder.translate_is_op(eqval, not_implemented, 'is', line), not_implemented_block, regular_block, Branch.BOOL_EXPR)) builder.activate_block(regular_block) retval = builder.coerce( builder.unary_op(eqval, 'not', line), object_rprimitive, line ) builder.add(Return(retval)) builder.activate_block(not_implemented_block) builder.add(Return(not_implemented)) blocks, env, ret_type, _ = builder.leave() return FuncIR( FuncDecl('__ne__', cls.name, builder.module_name, FuncSignature(rt_args, ret_type)), blocks, env)
def setUp(self) -> None: self.registers = [] # type: List[Register] def add_local(name: str, rtype: RType) -> Register: reg = Register(rtype, name) self.registers.append(reg) return reg self.n = add_local('n', int_rprimitive) self.m = add_local('m', int_rprimitive) self.k = add_local('k', int_rprimitive) self.l = add_local('l', list_rprimitive) # noqa self.ll = add_local('ll', list_rprimitive) self.o = add_local('o', object_rprimitive) self.o2 = add_local('o2', object_rprimitive) self.d = add_local('d', dict_rprimitive) self.b = add_local('b', bool_rprimitive) self.s1 = add_local('s1', short_int_rprimitive) self.s2 = add_local('s2', short_int_rprimitive) self.i32 = add_local('i32', int32_rprimitive) self.i32_1 = add_local('i32_1', int32_rprimitive) self.i64 = add_local('i64', int64_rprimitive) self.i64_1 = add_local('i64_1', int64_rprimitive) self.ptr = add_local('ptr', pointer_rprimitive) self.t = add_local('t', RTuple([int_rprimitive, bool_rprimitive])) self.tt = add_local( 'tt', RTuple( [RTuple([int_rprimitive, bool_rprimitive]), bool_rprimitive])) ir = ClassIR('A', 'mod') ir.attributes = OrderedDict([('x', bool_rprimitive), ('y', int_rprimitive)]) compute_vtable(ir) ir.mro = [ir] self.r = add_local('r', RInstance(ir)) self.context = EmitterContext(NameGenerator([['mod']]))
def add_self_to_env(environment: Environment, cls: ClassIR) -> AssignmentTargetRegister: return environment.add_local_reg(Var(SELF_NAME), RInstance(cls), is_arg=True)
def prepare_class_def(path: str, module_name: str, cdef: ClassDef, errors: Errors, mapper: Mapper) -> None: ir = mapper.type_to_ir[cdef.info] info = cdef.info attrs = get_mypyc_attrs(cdef) if attrs.get("allow_interpreted_subclasses") is True: ir.allow_interpreted_subclasses = True if attrs.get("serializable") is True: # Supports copy.copy and pickle (including subclasses) ir._serializable = True # We sort the table for determinism here on Python 3.5 for name, node in sorted(info.names.items()): # Currently all plugin generated methods are dummies and not included. if node.plugin_generated: continue if isinstance(node.node, Var): assert node.node.type, "Class member %s missing type" % name if not node.node.is_classvar and name not in ('__slots__', '__deletable__'): ir.attributes[name] = mapper.type_to_rtype(node.node.type) elif isinstance(node.node, (FuncDef, Decorator)): prepare_method_def(ir, module_name, cdef, mapper, node.node) elif isinstance(node.node, OverloadedFuncDef): # Handle case for property with both a getter and a setter if node.node.is_property: if is_valid_multipart_property_def(node.node): for item in node.node.items: prepare_method_def(ir, module_name, cdef, mapper, item) else: errors.error("Unsupported property decorator semantics", path, cdef.line) # Handle case for regular function overload else: assert node.node.impl prepare_method_def(ir, module_name, cdef, mapper, node.node.impl) # Check for subclassing from builtin types for cls in info.mro: # Special case exceptions and dicts # XXX: How do we handle *other* things?? if cls.fullname == 'builtins.BaseException': ir.builtin_base = 'PyBaseExceptionObject' elif cls.fullname == 'builtins.dict': ir.builtin_base = 'PyDictObject' elif cls.fullname.startswith('builtins.'): if not can_subclass_builtin(cls.fullname): # Note that if we try to subclass a C extension class that # isn't in builtins, bad things will happen and we won't # catch it here! But this should catch a lot of the most # common pitfalls. errors.error("Inheriting from most builtin types is unimplemented", path, cdef.line) if ir.builtin_base: ir.attributes.clear() # Set up a constructor decl init_node = cdef.info['__init__'].node if not ir.is_trait and not ir.builtin_base and isinstance(init_node, FuncDef): init_sig = mapper.fdef_to_sig(init_node) defining_ir = mapper.type_to_ir.get(init_node.info) # If there is a nontrivial __init__ that wasn't defined in an # extension class, we need to make the constructor take *args, # **kwargs so it can call tp_init. if ((defining_ir is None or not defining_ir.is_ext_class or cdef.info['__init__'].plugin_generated) and init_node.info.fullname != 'builtins.object'): init_sig = FuncSignature( [init_sig.args[0], RuntimeArg("args", tuple_rprimitive, ARG_STAR), RuntimeArg("kwargs", dict_rprimitive, ARG_STAR2)], init_sig.ret_type) ctor_sig = FuncSignature(init_sig.args[1:], RInstance(ir)) ir.ctor = FuncDecl(cdef.name, None, module_name, ctor_sig) mapper.func_to_decl[cdef.info] = ir.ctor # Set up the parent class bases = [mapper.type_to_ir[base.type] for base in info.bases if base.type in mapper.type_to_ir] if not all(c.is_trait for c in bases[1:]): errors.error("Non-trait bases must appear first in parent list", path, cdef.line) ir.traits = [c for c in bases if c.is_trait] mro = [] base_mro = [] for cls in info.mro: if cls not in mapper.type_to_ir: if cls.fullname != 'builtins.object': ir.inherits_python = True continue base_ir = mapper.type_to_ir[cls] if not base_ir.is_trait: base_mro.append(base_ir) mro.append(base_ir) if cls.defn.removed_base_type_exprs or not base_ir.is_ext_class: ir.inherits_python = True base_idx = 1 if not ir.is_trait else 0 if len(base_mro) > base_idx: ir.base = base_mro[base_idx] ir.mro = mro ir.base_mro = base_mro for base in bases: if base.children is not None: base.children.append(ir) if is_dataclass(cdef): ir.is_augmented = True
def add_self_to_env(self, cls: ClassIR) -> AssignmentTargetRegister: """Low-level function that adds a 'self' argument. This is only useful if using enter() instead of enter_method(). """ return self.add_local_reg(Var(SELF_NAME), RInstance(cls), is_arg=True)
def type_to_rtype(self, typ: Optional[Type]) -> 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.str': return str_rprimitive elif typ.type.fullname == 'builtins.bool': return bool_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 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 __init__(self, name: str, module_name: str, is_trait: bool = False, is_generated: bool = False, is_abstract: bool = False, is_ext_class: bool = True) -> None: self.name = name self.module_name = module_name self.is_trait = is_trait self.is_generated = is_generated self.is_abstract = is_abstract self.is_ext_class = is_ext_class # An augmented class has additional methods separate from what mypyc generates. # Right now the only one is dataclasses. self.is_augmented = False # Does this inherit from a Python class? self.inherits_python = False # Do instances of this class have __dict__? self.has_dict = False # Do we allow interpreted subclasses? Derived from a mypyc_attr. self.allow_interpreted_subclasses = False # Does this class need getseters to be generated for its attributes? (getseters are also # added if is_generated is False) self.needs_getseters = False # Is this class declared as serializable (supports copy.copy # and pickle) using @mypyc_attr(serializable=True)? # # Additionally, any class with this attribute False but with # an __init__ that can be called without any arguments is # *implicitly serializable*. In this case __init__ will be # called during deserialization without arguments. If this is # True, we match Python semantics and __init__ won't be called # during deserialization. # # This impacts also all subclasses. Use is_serializable() to # also consider base classes. self._serializable = False # If this a subclass of some built-in python class, the name # of the object for that class. We currently only support this # in a few ad-hoc cases. self.builtin_base: Optional[str] = None # Default empty constructor self.ctor = FuncDecl(name, None, module_name, FuncSignature([], RInstance(self))) self.attributes: OrderedDict[str, RType] = OrderedDict() # Deletable attributes self.deletable: List[str] = [] # We populate method_types with the signatures of every method before # we generate methods, and we rely on this information being present. self.method_decls: OrderedDict[str, FuncDecl] = OrderedDict() # Map of methods that are actually present in an extension class self.methods: OrderedDict[str, FuncIR] = OrderedDict() # Glue methods for boxing/unboxing when a class changes the type # while overriding a method. Maps from (parent class overridden, method) # to IR of glue method. self.glue_methods: Dict[Tuple[ClassIR, str], FuncIR] = OrderedDict() # Properties are accessed like attributes, but have behavior like method calls. # They don't belong in the methods dictionary, since we don't want to expose them to # Python's method API. But we want to put them into our own vtable as methods, so that # they are properly handled and overridden. The property dictionary values are a tuple # containing a property getter and an optional property setter. self.properties: OrderedDict[str, Tuple[FuncIR, Optional[FuncIR]]] = OrderedDict() # We generate these in prepare_class_def so that we have access to them when generating # other methods and properties that rely on these types. self.property_types: OrderedDict[str, RType] = OrderedDict() self.vtable: Optional[Dict[str, int]] = None self.vtable_entries: VTableEntries = [] self.trait_vtables: OrderedDict[ClassIR, VTableEntries] = OrderedDict() # N.B: base might not actually quite be the direct base. # It is the nearest concrete base, but we allow a trait in between. self.base: Optional[ClassIR] = None self.traits: List[ClassIR] = [] # Supply a working mro for most generated classes. Real classes will need to # fix it up. self.mro: List[ClassIR] = [self] # base_mro is the chain of concrete (non-trait) ancestors self.base_mro: List[ClassIR] = [self] # Direct subclasses of this class (use subclasses() to also include non-direct ones) # None if separate compilation prevents this from working self.children: Optional[List[ClassIR]] = [] # Instance attributes that are initialized in the class body. self.attrs_with_defaults: Set[str] = set() # Attributes that are always initialized in __init__ or class body # (inferred in mypyc.analysis.attrdefined using interprocedural analysis) self._always_initialized_attrs: Set[str] = set() # Attributes that are sometimes initialized in __init__ self._sometimes_initialized_attrs: Set[str] = set() # If True, __init__ can make 'self' visible to unanalyzed/arbitrary code self.init_self_leak = False
def __init__(self, name: str, module_name: str, is_trait: bool = False, is_generated: bool = False, is_abstract: bool = False, is_ext_class: bool = True) -> None: self.name = name self.module_name = module_name self.is_trait = is_trait self.is_generated = is_generated self.is_abstract = is_abstract self.is_ext_class = is_ext_class # An augmented class has additional methods separate from what mypyc generates. # Right now the only one is dataclasses. self.is_augmented = False # Does this inherit from a Python class? self.inherits_python = False # Do instances of this class have __dict__? self.has_dict = False # Do we allow interpreted subclasses? Derived from a mypyc_attr. self.allow_interpreted_subclasses = False # If this a subclass of some built-in python class, the name # of the object for that class. We currently only support this # in a few ad-hoc cases. self.builtin_base = None # type: Optional[str] # Default empty constructor self.ctor = FuncDecl(name, None, module_name, FuncSignature([], RInstance(self))) self.attributes = OrderedDict() # type: OrderedDict[str, RType] # We populate method_types with the signatures of every method before # we generate methods, and we rely on this information being present. self.method_decls = OrderedDict() # type: OrderedDict[str, FuncDecl] # Map of methods that are actually present in an extension class self.methods = OrderedDict() # type: OrderedDict[str, FuncIR] # Glue methods for boxing/unboxing when a class changes the type # while overriding a method. Maps from (parent class overrided, method) # to IR of glue method. self.glue_methods = OrderedDict() # type: Dict[Tuple[ClassIR, str], FuncIR] # Properties are accessed like attributes, but have behavior like method calls. # They don't belong in the methods dictionary, since we don't want to expose them to # Python's method API. But we want to put them into our own vtable as methods, so that # they are properly handled and overridden. The property dictionary values are a tuple # containing a property getter and an optional property setter. self.properties = OrderedDict() # type: OrderedDict[str, Tuple[FuncIR, Optional[FuncIR]]] # We generate these in prepare_class_def so that we have access to them when generating # other methods and properties that rely on these types. self.property_types = OrderedDict() # type: OrderedDict[str, RType] self.vtable = None # type: Optional[Dict[str, int]] self.vtable_entries = [] # type: VTableEntries self.trait_vtables = OrderedDict() # type: OrderedDict[ClassIR, VTableEntries] # N.B: base might not actually quite be the direct base. # It is the nearest concrete base, but we allow a trait in between. self.base = None # type: Optional[ClassIR] self.traits = [] # type: List[ClassIR] # Supply a working mro for most generated classes. Real classes will need to # fix it up. self.mro = [self] # type: List[ClassIR] # base_mro is the chain of concrete (non-trait) ancestors self.base_mro = [self] # type: List[ClassIR] # Direct subclasses of this class (use subclasses() to also incude non-direct ones) # None if separate compilation prevents this from working self.children = [] # type: Optional[List[ClassIR]]
def generate_attr_defaults(builder: IRBuilder, cdef: ClassDef) -> None: """Generate an initialization method for default attr values (from class vars).""" cls = builder.mapper.type_to_ir[cdef.info] if cls.builtin_base: return # Pull out all assignments in classes in the mro so we can initialize them # TODO: Support nested statements default_assignments = [] for info in reversed(cdef.info.mro): if info not in builder.mapper.type_to_ir: continue for stmt in info.defn.defs.body: if (isinstance(stmt, AssignmentStmt) and isinstance(stmt.lvalues[0], NameExpr) and not is_class_var(stmt.lvalues[0]) and not isinstance(stmt.rvalue, TempNode)): if stmt.lvalues[0].name == '__slots__': continue # Skip type annotated assignments in dataclasses if is_dataclass(cdef) and stmt.type: continue default_assignments.append(stmt) if not default_assignments: return builder.enter() builder.ret_types[-1] = bool_rprimitive rt_args = (RuntimeArg(SELF_NAME, RInstance(cls)), ) self_var = builder.read(add_self_to_env(builder.environment, cls), -1) for stmt in default_assignments: lvalue = stmt.lvalues[0] assert isinstance(lvalue, NameExpr) if not stmt.is_final_def and not is_constant(stmt.rvalue): builder.warning('Unsupported default attribute value', stmt.rvalue.line) # If the attribute is initialized to None and type isn't optional, # don't initialize it to anything. attr_type = cls.attr_type(lvalue.name) if isinstance(stmt.rvalue, RefExpr) and stmt.rvalue.fullname == 'builtins.None': if (not is_optional_type(attr_type) and not is_object_rprimitive(attr_type) and not is_none_rprimitive(attr_type)): continue val = builder.coerce(builder.accept(stmt.rvalue), attr_type, stmt.line) builder.add(SetAttr(self_var, lvalue.name, val, -1)) builder.add(Return(builder.true())) blocks, env, ret_type, _ = builder.leave() ir = FuncIR( FuncDecl('__mypyc_defaults_setup', cls.name, builder.module_name, FuncSignature(rt_args, ret_type)), blocks, env) builder.functions.append(ir) cls.methods[ir.name] = ir
def gen_glue_method( builder: IRBuilder, sig: FuncSignature, target: FuncIR, cls: ClassIR, base: ClassIR, line: int, do_pycall: bool, ) -> FuncIR: """Generate glue methods that mediate between different method types in subclasses. For example, if we have: class A: def f(builder: IRBuilder, x: int) -> object: ... then it is totally permissible to have a subclass class B(A): def f(builder: IRBuilder, x: object) -> int: ... since '(object) -> int' is a subtype of '(int) -> object' by the usual contra/co-variant function subtyping rules. The trickiness here is that int and object have different runtime representations in mypyc, so A.f and B.f have different signatures at the native C level. To deal with this, we need to generate glue methods that mediate between the different versions by coercing the arguments and return values. If do_pycall is True, then make the call using the C API instead of a native call. """ builder.enter() builder.ret_types[-1] = sig.ret_type rt_args = list(sig.args) if target.decl.kind == FUNC_NORMAL: rt_args[0] = RuntimeArg(sig.args[0].name, RInstance(cls)) arg_info = get_args(builder, rt_args, line) args, arg_kinds, arg_names = arg_info.args, arg_info.arg_kinds, arg_info.arg_names # We can do a passthrough *args/**kwargs with a native call, but if the # args need to get distributed out to arguments, we just let python handle it if (any(kind.is_star() for kind in arg_kinds) and any(not arg.kind.is_star() for arg in target.decl.sig.args)): do_pycall = True if do_pycall: if target.decl.kind == FUNC_STATICMETHOD: # FIXME: this won't work if we can do interpreted subclasses first = builder.builder.get_native_type(cls) st = 0 else: first = args[0] st = 1 retval = builder.builder.py_method_call(first, target.name, args[st:], line, arg_kinds[st:], arg_names[st:]) else: retval = builder.builder.call(target.decl, args, arg_kinds, arg_names, line) retval = builder.coerce(retval, sig.ret_type, line) builder.add(Return(retval)) arg_regs, _, blocks, ret_type, _ = builder.leave() return FuncIR( FuncDecl(target.name + '__' + base.name + '_glue', cls.name, builder.module_name, FuncSignature(rt_args, ret_type), target.decl.kind), arg_regs, blocks)
def setUp(self) -> None: self.inst_a = RInstance(ClassIR('A', '__main__')) self.inst_b = RInstance(ClassIR('B', '__main__'))