def test_call_two_args(self) -> None: decl = FuncDecl('myfn', None, 'mod', FuncSignature([RuntimeArg('m', int_rprimitive), RuntimeArg('n', int_rprimitive)], int_rprimitive)) self.assert_emit(Call(decl, [self.m, self.k], 55), "cpy_r_r0 = CPyDef_myfn(cpy_r_m, cpy_r_k);")
def add_send_to_generator_class(builder: IRBuilder, fn_info: FuncInfo, fn_decl: FuncDecl, sig: FuncSignature) -> None: """Generates the 'send' method for a generator class.""" # FIXME: this is basically the same as add_next... builder.enter(fn_info) self_reg = builder.read( add_self_to_env(builder.environment, fn_info.generator_class.ir)) arg = builder.environment.add_local_reg(Var('arg'), object_rprimitive, True) none_reg = builder.none_object() # Call the helper function with error flags set to Py_None, and return that result. result = builder.add( Call(fn_decl, [self_reg, none_reg, none_reg, none_reg, builder.read(arg)], fn_info.fitem.line)) builder.add(Return(result)) blocks, env, _, fn_info = builder.leave() sig = FuncSignature(( RuntimeArg(SELF_NAME, object_rprimitive), RuntimeArg('arg', object_rprimitive), ), sig.ret_type) next_fn_decl = FuncDecl('send', fn_info.generator_class.ir.name, builder.module_name, sig) next_fn_ir = FuncIR(next_fn_decl, blocks, env) fn_info.generator_class.ir.methods['send'] = next_fn_ir builder.functions.append(next_fn_ir)
def add_throw_to_generator_class(builder: IRBuilder, fn_info: FuncInfo, fn_decl: FuncDecl, sig: FuncSignature) -> None: """Generates the 'throw' method for a generator class.""" builder.enter(fn_info) self_reg = builder.read( add_self_to_env(builder.environment, fn_info.generator_class.ir)) # Add the type, value, and traceback variables to the environment. typ = builder.environment.add_local_reg(Var('type'), object_rprimitive, True) val = builder.environment.add_local_reg(Var('value'), object_rprimitive, True) tb = builder.environment.add_local_reg(Var('traceback'), object_rprimitive, True) # Because the value and traceback arguments are optional and hence # can be NULL if not passed in, we have to assign them Py_None if # they are not passed in. none_reg = builder.none_object() builder.assign_if_null(val, lambda: none_reg, builder.fn_info.fitem.line) builder.assign_if_null(tb, lambda: none_reg, builder.fn_info.fitem.line) # Call the helper function using the arguments passed in, and return that result. result = builder.add( Call(fn_decl, [ self_reg, builder.read(typ), builder.read(val), builder.read(tb), none_reg ], fn_info.fitem.line)) builder.add(Return(result)) blocks, env, _, fn_info = builder.leave() # Create the FuncSignature for the throw function. Note that the # value and traceback fields are optional, and are assigned to if # they are not passed in inside the body of the throw function. sig = FuncSignature((RuntimeArg( SELF_NAME, object_rprimitive), RuntimeArg('type', object_rprimitive), RuntimeArg('value', object_rprimitive, ARG_OPT), RuntimeArg('traceback', object_rprimitive, ARG_OPT)), sig.ret_type) throw_fn_decl = FuncDecl('throw', fn_info.generator_class.ir.name, builder.module_name, sig) throw_fn_ir = FuncIR(throw_fn_decl, blocks, env) fn_info.generator_class.ir.methods['throw'] = throw_fn_ir builder.functions.append(throw_fn_ir)
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) ] ret = self.type_to_rtype(fdef.type.ret_type) else: # Handle unannotated functions arg_types = [object_rprimitive 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 args = [ RuntimeArg(arg_name, arg_type, arg_kind) for arg_name, arg_kind, arg_type in zip( fdef.arg_names, fdef.arg_kinds, arg_types) ] # 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 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 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 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 add_argument(self, var: Union[str, Var], typ: RType, kind: int = ARG_POS) -> Register: """Declare an argument in the current function. You should use this instead of directly calling add_local() in new code. """ if isinstance(var, str): var = Var(var) reg = self.add_local(var, typ, is_arg=True) self.runtime_args[-1].append(RuntimeArg(var.name, typ, kind)) return reg
def add_helper_to_generator_class(builder: IRBuilder, arg_regs: List[Register], blocks: List[BasicBlock], sig: FuncSignature, fn_info: FuncInfo) -> FuncDecl: """Generates a helper method for a generator class, called by '__next__' and 'throw'.""" sig = FuncSignature((RuntimeArg(SELF_NAME, object_rprimitive), RuntimeArg('type', object_rprimitive), RuntimeArg('value', object_rprimitive), RuntimeArg('traceback', object_rprimitive), RuntimeArg('arg', object_rprimitive) ), sig.ret_type) helper_fn_decl = FuncDecl('__mypyc_generator_helper__', fn_info.generator_class.ir.name, builder.module_name, sig) helper_fn_ir = FuncIR(helper_fn_decl, arg_regs, blocks, fn_info.fitem.line, traceback_name=fn_info.fitem.name) fn_info.generator_class.ir.methods['__mypyc_generator_helper__'] = helper_fn_ir builder.functions.append(helper_fn_ir) return helper_fn_decl
def add_get_to_callable_class(builder: IRBuilder, fn_info: FuncInfo) -> None: """Generate the '__get__' method for a callable class.""" line = fn_info.fitem.line builder.enter(fn_info) vself = builder.read( builder.environment.add_local_reg(Var(SELF_NAME), object_rprimitive, True)) instance = builder.environment.add_local_reg(Var('instance'), object_rprimitive, True) builder.environment.add_local_reg(Var('owner'), object_rprimitive, True) # If accessed through the class, just return the callable # object. If accessed through an object, create a new bound # instance method object. instance_block, class_block = BasicBlock(), BasicBlock() comparison = builder.translate_is_op(builder.read(instance), builder.none_object(), 'is', line) builder.add_bool_branch(comparison, class_block, instance_block) builder.activate_block(class_block) builder.add(Return(vself)) builder.activate_block(instance_block) builder.add( Return( builder.call_c(method_new_op, [vself, builder.read(instance)], line))) blocks, env, _, fn_info = builder.leave() sig = FuncSignature((RuntimeArg(SELF_NAME, object_rprimitive), RuntimeArg('instance', object_rprimitive), RuntimeArg('owner', object_rprimitive)), object_rprimitive) get_fn_decl = FuncDecl('__get__', fn_info.callable_class.ir.name, builder.module_name, sig) get_fn_ir = FuncIR(get_fn_decl, blocks, env) fn_info.callable_class.ir.methods['__get__'] = get_fn_ir builder.functions.append(get_fn_ir)
def add_iter_to_generator_class(builder: IRBuilder, fn_info: FuncInfo) -> None: """Generates the '__iter__' method for a generator class.""" builder.enter(fn_info) self_target = add_self_to_env(builder.environment, fn_info.generator_class.ir) builder.add(Return(builder.read(self_target, fn_info.fitem.line))) blocks, env, _, fn_info = builder.leave() # Next, add the actual function as a method of the generator class. sig = FuncSignature((RuntimeArg(SELF_NAME, object_rprimitive), ), object_rprimitive) iter_fn_decl = FuncDecl('__iter__', fn_info.generator_class.ir.name, builder.module_name, sig) iter_fn_ir = FuncIR(iter_fn_decl, blocks, env) fn_info.generator_class.ir.methods['__iter__'] = iter_fn_ir builder.functions.append(iter_fn_ir)
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 arg 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 add_call_to_callable_class(builder: IRBuilder, blocks: List[BasicBlock], sig: FuncSignature, env: Environment, fn_info: FuncInfo) -> FuncIR: """Generates a '__call__' method for a callable class representing a nested function. This takes the blocks, signature, and environment associated with a function definition and uses those to build the '__call__' method of a given callable class, used to represent that function. Note that a 'self' parameter is added to its list of arguments, as the nested function becomes a class method. """ sig = FuncSignature((RuntimeArg(SELF_NAME, object_rprimitive),) + sig.args, sig.ret_type) call_fn_decl = FuncDecl('__call__', fn_info.callable_class.ir.name, builder.module_name, sig) call_fn_ir = FuncIR(call_fn_decl, blocks, env, fn_info.fitem.line, traceback_name=fn_info.fitem.name) fn_info.callable_class.ir.methods['__call__'] = call_fn_ir return call_fn_ir
def add_call_to_callable_class(builder: IRBuilder, args: List[Register], blocks: List[BasicBlock], sig: FuncSignature, fn_info: FuncInfo) -> FuncIR: """Generate a '__call__' method for a callable class representing a nested function. This takes the blocks and signature associated with a function definition and uses those to build the '__call__' method of a given callable class, used to represent that function. """ # Since we create a method, we also add a 'self' parameter. sig = FuncSignature((RuntimeArg(SELF_NAME, object_rprimitive),) + sig.args, sig.ret_type) call_fn_decl = FuncDecl('__call__', fn_info.callable_class.ir.name, builder.module_name, sig) call_fn_ir = FuncIR(call_fn_decl, args, blocks, fn_info.fitem.line, traceback_name=fn_info.fitem.name) fn_info.callable_class.ir.methods['__call__'] = call_fn_ir fn_info.callable_class.ir.method_decls['__call__'] = call_fn_decl return call_fn_ir
def transform_lambda_expr(builder: IRBuilder, expr: LambdaExpr) -> Value: typ = get_proper_type(builder.types[expr]) assert isinstance(typ, CallableType) runtime_args = [] for arg, arg_type in zip(expr.arguments, typ.arg_types): arg.variable.type = arg_type runtime_args.append( RuntimeArg(arg.variable.name, builder.type_to_rtype(arg_type), arg.kind)) ret_type = builder.type_to_rtype(typ.ret_type) fsig = FuncSignature(runtime_args, ret_type) fname = '{}{}'.format(LAMBDA_NAME, builder.lambda_counter) builder.lambda_counter += 1 func_ir, func_reg = gen_func_item(builder, expr, fname, fsig) assert func_reg is not None builder.functions.append(func_ir) return func_reg
def add_close_to_generator_class(builder: IRBuilder, fn_info: FuncInfo) -> None: """Generates the '__close__' method for a generator class.""" # TODO: Currently this method just triggers a runtime error, # we should fill this out eventually. builder.enter(fn_info) add_self_to_env(builder.environment, fn_info.generator_class.ir) builder.add( RaiseStandardError(RaiseStandardError.RUNTIME_ERROR, 'close method on generator classes uimplemented', fn_info.fitem.line)) builder.add(Unreachable()) blocks, env, _, fn_info = builder.leave() # Next, add the actual function as a method of the generator class. sig = FuncSignature((RuntimeArg(SELF_NAME, object_rprimitive), ), object_rprimitive) close_fn_decl = FuncDecl('close', fn_info.generator_class.ir.name, builder.module_name, sig) close_fn_ir = FuncIR(close_fn_decl, blocks, env) fn_info.generator_class.ir.methods['close'] = close_fn_ir builder.functions.append(close_fn_ir)
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 setUp(self) -> None: self.arg = RuntimeArg('arg', int_rprimitive) self.reg = Register(int_rprimitive, 'arg') self.block = BasicBlock(0)
def setUp(self) -> None: self.var = Var('arg') self.arg = RuntimeArg('arg', int_rprimitive) self.env = Environment() self.reg = self.env.add_local(self.var, int_rprimitive) self.block = BasicBlock(0)
def generate_set_del_item_wrapper(cl: ClassIR, fn: FuncIR, emitter: Emitter) -> str: """Generates a wrapper for native __setitem__ method (also works for __delitem__). This is used with the mapping protocol slot. Arguments are taken as *PyObjects and we return a negative C int on error. Create a separate wrapper function for __delitem__ as needed and have the __setitem__ wrapper call it if the value is NULL. Return the name of the outer (__setitem__) wrapper. """ method_cls = cl.get_method_and_class('__delitem__') del_name = None if method_cls and method_cls[1] == cl: # Generate a separate wrapper for __delitem__ del_name = generate_del_item_wrapper(cl, method_cls[0], emitter) args = fn.args if fn.name == '__delitem__': # Add an extra argument for value that we expect to be NULL. args = list(args) + [RuntimeArg('___value', object_rprimitive, ARG_POS)] name = '{}{}{}'.format(DUNDER_PREFIX, '__setitem__', cl.name_prefix(emitter.names)) input_args = ', '.join('PyObject *obj_{}'.format(arg.name) for arg in args) emitter.emit_line('static int {name}({input_args}) {{'.format( name=name, input_args=input_args, )) # First check if this is __delitem__ emitter.emit_line('if (obj_{} == NULL) {{'.format(args[2].name)) if del_name is not None: # We have a native implementation, so call it emitter.emit_line('return {}(obj_{}, obj_{});'.format(del_name, args[0].name, args[1].name)) else: # Try to call superclass method instead emitter.emit_line( 'PyObject *super = CPy_Super(CPyModule_builtins, obj_{});'.format(args[0].name)) emitter.emit_line('if (super == NULL) return -1;') emitter.emit_line( 'PyObject *result = PyObject_CallMethod(super, "__delitem__", "O", obj_{});'.format( args[1].name)) emitter.emit_line('Py_DECREF(super);') emitter.emit_line('Py_XDECREF(result);') emitter.emit_line('return result == NULL ? -1 : 0;') emitter.emit_line('}') method_cls = cl.get_method_and_class('__setitem__') if method_cls and method_cls[1] == cl: generate_set_del_item_wrapper_inner(fn, emitter, args) else: emitter.emit_line( 'PyObject *super = CPy_Super(CPyModule_builtins, obj_{});'.format(args[0].name)) emitter.emit_line('if (super == NULL) return -1;') emitter.emit_line('PyObject *result;') if method_cls is None and cl.builtin_base is None: msg = "'{}' object does not support item assignment".format(cl.name) emitter.emit_line( 'PyErr_SetString(PyExc_TypeError, "{}");'.format(msg)) emitter.emit_line('result = NULL;') else: # A base class may have __setitem__ emitter.emit_line( 'result = PyObject_CallMethod(super, "__setitem__", "OO", obj_{}, obj_{});'.format( args[1].name, args[2].name)) emitter.emit_line('Py_DECREF(super);') emitter.emit_line('Py_XDECREF(result);') emitter.emit_line('return result == NULL ? -1 : 0;') emitter.emit_line('}') return name
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)