| """Transform mypy AST functions to IR (and related things). |
| |
| Normal functions are translated into a list of basic blocks |
| containing various IR ops (defined in mypyc.ir.ops). |
| |
| This also deals with generators, async functions and nested |
| functions. All of these are transformed into callable classes. These |
| have a custom __call__ method that implements the call, and state, such |
| as an environment containing non-local variables, is stored in the |
| instance of the callable class. |
| """ |
| |
| from typing import ( |
| DefaultDict, NamedTuple, Optional, List, Sequence, Tuple, Union, Dict, |
| ) |
| |
| from mypy.nodes import ( |
| ClassDef, FuncDef, OverloadedFuncDef, Decorator, Var, YieldFromExpr, AwaitExpr, YieldExpr, |
| FuncItem, LambdaExpr, SymbolNode, ArgKind, TypeInfo |
| ) |
| from mypy.types import CallableType, get_proper_type |
| |
| from mypyc.ir.ops import ( |
| BasicBlock, Value, Register, Return, SetAttr, Integer, GetAttr, Branch, InitStatic, |
| LoadAddress, LoadLiteral, Unbox, Unreachable, |
| ) |
| from mypyc.ir.rtypes import ( |
| object_rprimitive, RInstance, object_pointer_rprimitive, dict_rprimitive, int_rprimitive, |
| bool_rprimitive, |
| ) |
| from mypyc.ir.func_ir import ( |
| FuncIR, FuncSignature, RuntimeArg, FuncDecl, FUNC_CLASSMETHOD, FUNC_STATICMETHOD, FUNC_NORMAL |
| ) |
| from mypyc.ir.class_ir import ClassIR, NonExtClassInfo |
| from mypyc.primitives.generic_ops import py_setattr_op, next_raw_op, iter_op |
| from mypyc.primitives.misc_ops import ( |
| check_stop_op, yield_from_except_op, coro_op, send_op, register_function |
| ) |
| from mypyc.primitives.dict_ops import dict_set_item_op, dict_new_op, dict_get_method_with_none |
| from mypyc.common import SELF_NAME, LAMBDA_NAME |
| from mypyc.sametype import is_same_method_signature |
| from mypyc.irbuild.util import is_constant |
| from mypyc.irbuild.context import FuncInfo, ImplicitClass |
| from mypyc.irbuild.targets import AssignmentTarget |
| from mypyc.irbuild.statement import transform_try_except |
| from mypyc.irbuild.builder import IRBuilder, SymbolTarget, gen_arg_defaults |
| from mypyc.irbuild.callable_class import ( |
| setup_callable_class, add_call_to_callable_class, add_get_to_callable_class, |
| instantiate_callable_class |
| ) |
| from mypyc.irbuild.generator import ( |
| gen_generator_func, setup_env_for_generator_class, create_switch_for_generator_class, |
| add_raise_exception_blocks_to_generator_class, populate_switch_for_generator_class, |
| add_methods_to_generator_class |
| ) |
| from mypyc.irbuild.env_class import ( |
| setup_env_class, load_outer_envs, load_env_registers, finalize_env_class, |
| setup_func_for_recursive_call |
| ) |
| |
| from mypyc.primitives.registry import builtin_names |
| from collections import defaultdict |
| |
| |
| # Top-level transform functions |
| |
| |
| def transform_func_def(builder: IRBuilder, fdef: FuncDef) -> None: |
| func_ir, func_reg = gen_func_item(builder, fdef, fdef.name, builder.mapper.fdef_to_sig(fdef)) |
| |
| # If the function that was visited was a nested function, then either look it up in our |
| # current environment or define it if it was not already defined. |
| if func_reg: |
| builder.assign(get_func_target(builder, fdef), func_reg, fdef.line) |
| maybe_insert_into_registry_dict(builder, fdef) |
| builder.functions.append(func_ir) |
| |
| |
| def transform_overloaded_func_def(builder: IRBuilder, o: OverloadedFuncDef) -> None: |
| # Handle regular overload case |
| assert o.impl |
| builder.accept(o.impl) |
| |
| |
| def transform_decorator(builder: IRBuilder, dec: Decorator) -> None: |
| func_ir, func_reg = gen_func_item( |
| builder, |
| dec.func, |
| dec.func.name, |
| builder.mapper.fdef_to_sig(dec.func) |
| ) |
| decorated_func: Optional[Value] = None |
| if func_reg: |
| decorated_func = load_decorated_func(builder, dec.func, func_reg) |
| builder.assign(get_func_target(builder, dec.func), decorated_func, dec.func.line) |
| func_reg = decorated_func |
| # If the prebuild pass didn't put this function in the function to decorators map (for example |
| # if this is a registered singledispatch implementation with no other decorators), we should |
| # treat this function as a regular function, not a decorated function |
| elif dec.func in builder.fdefs_to_decorators: |
| # Obtain the the function name in order to construct the name of the helper function. |
| name = dec.func.fullname.split('.')[-1] |
| |
| # Load the callable object representing the non-decorated function, and decorate it. |
| orig_func = builder.load_global_str(name, dec.line) |
| decorated_func = load_decorated_func(builder, dec.func, orig_func) |
| |
| if decorated_func is not None: |
| # Set the callable object representing the decorated function as a global. |
| builder.call_c(dict_set_item_op, |
| [builder.load_globals_dict(), |
| builder.load_str(dec.func.name), decorated_func], |
| decorated_func.line) |
| |
| maybe_insert_into_registry_dict(builder, dec.func) |
| |
| builder.functions.append(func_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 transform_yield_expr(builder: IRBuilder, expr: YieldExpr) -> Value: |
| if builder.fn_info.is_coroutine: |
| builder.error('async generators are unimplemented', expr.line) |
| |
| if expr.expr: |
| retval = builder.accept(expr.expr) |
| else: |
| retval = builder.builder.none() |
| return emit_yield(builder, retval, expr.line) |
| |
| |
| def transform_yield_from_expr(builder: IRBuilder, o: YieldFromExpr) -> Value: |
| return handle_yield_from_and_await(builder, o) |
| |
| |
| def transform_await_expr(builder: IRBuilder, o: AwaitExpr) -> Value: |
| return handle_yield_from_and_await(builder, o) |
| |
| |
| # Internal functions |
| |
| |
| def gen_func_item(builder: IRBuilder, |
| fitem: FuncItem, |
| name: str, |
| sig: FuncSignature, |
| cdef: Optional[ClassDef] = None, |
| ) -> Tuple[FuncIR, Optional[Value]]: |
| """Generate and return the FuncIR for a given FuncDef. |
| |
| If the given FuncItem is a nested function, then we generate a |
| callable class representing the function and use that instead of |
| the actual function. if the given FuncItem contains a nested |
| function, then we generate an environment class so that inner |
| nested functions can access the environment of the given FuncDef. |
| |
| Consider the following nested function: |
| |
| def a() -> None: |
| def b() -> None: |
| def c() -> None: |
| return None |
| return None |
| return None |
| |
| The classes generated would look something like the following. |
| |
| has pointer to +-------+ |
| +--------------------------> | a_env | |
| | +-------+ |
| | ^ |
| | | has pointer to |
| +-------+ associated with +-------+ |
| | b_obj | -------------------> | b_env | |
| +-------+ +-------+ |
| ^ |
| | |
| +-------+ has pointer to | |
| | c_obj | --------------------------+ |
| +-------+ |
| """ |
| |
| # TODO: do something about abstract methods. |
| |
| func_reg: Optional[Value] = None |
| |
| # We treat lambdas as always being nested because we always generate |
| # a class for lambdas, no matter where they are. (It would probably also |
| # work to special case toplevel lambdas and generate a non-class function.) |
| is_nested = fitem in builder.nested_fitems or isinstance(fitem, LambdaExpr) |
| contains_nested = fitem in builder.encapsulating_funcs.keys() |
| is_decorated = fitem in builder.fdefs_to_decorators |
| is_singledispatch = fitem in builder.singledispatch_impls |
| in_non_ext = False |
| class_name = None |
| if cdef: |
| ir = builder.mapper.type_to_ir[cdef.info] |
| in_non_ext = not ir.is_ext_class |
| class_name = cdef.name |
| |
| if is_singledispatch: |
| func_name = singledispatch_main_func_name(name) |
| else: |
| func_name = name |
| builder.enter(FuncInfo(fitem, func_name, class_name, gen_func_ns(builder), |
| is_nested, contains_nested, is_decorated, in_non_ext)) |
| |
| # Functions that contain nested functions need an environment class to store variables that |
| # are free in their nested functions. Generator functions need an environment class to |
| # store a variable denoting the next instruction to be executed when the __next__ function |
| # is called, along with all the variables inside the function itself. |
| if builder.fn_info.contains_nested or builder.fn_info.is_generator: |
| setup_env_class(builder) |
| |
| if builder.fn_info.is_nested or builder.fn_info.in_non_ext: |
| setup_callable_class(builder) |
| |
| if builder.fn_info.is_generator: |
| # Do a first-pass and generate a function that just returns a generator object. |
| gen_generator_func(builder) |
| args, _, blocks, ret_type, fn_info = builder.leave() |
| func_ir, func_reg = gen_func_ir( |
| builder, args, blocks, sig, fn_info, cdef, is_singledispatch, |
| ) |
| |
| # Re-enter the FuncItem and visit the body of the function this time. |
| builder.enter(fn_info) |
| setup_env_for_generator_class(builder) |
| load_outer_envs(builder, builder.fn_info.generator_class) |
| if builder.fn_info.is_nested and isinstance(fitem, FuncDef): |
| setup_func_for_recursive_call(builder, fitem, builder.fn_info.generator_class) |
| create_switch_for_generator_class(builder) |
| add_raise_exception_blocks_to_generator_class(builder, fitem.line) |
| else: |
| load_env_registers(builder) |
| gen_arg_defaults(builder) |
| |
| if builder.fn_info.contains_nested and not builder.fn_info.is_generator: |
| finalize_env_class(builder) |
| |
| builder.ret_types[-1] = sig.ret_type |
| |
| # Add all variables and functions that are declared/defined within this |
| # function and are referenced in functions nested within this one to this |
| # function's environment class so the nested functions can reference |
| # them even if they are declared after the nested function's definition. |
| # Note that this is done before visiting the body of this function. |
| |
| env_for_func: Union[FuncInfo, ImplicitClass] = builder.fn_info |
| if builder.fn_info.is_generator: |
| env_for_func = builder.fn_info.generator_class |
| elif builder.fn_info.is_nested or builder.fn_info.in_non_ext: |
| env_for_func = builder.fn_info.callable_class |
| |
| if builder.fn_info.fitem in builder.free_variables: |
| # Sort the variables to keep things deterministic |
| for var in sorted(builder.free_variables[builder.fn_info.fitem], |
| key=lambda x: x.name): |
| if isinstance(var, Var): |
| rtype = builder.type_to_rtype(var.type) |
| builder.add_var_to_env_class(var, rtype, env_for_func, reassign=False) |
| |
| if builder.fn_info.fitem in builder.encapsulating_funcs: |
| for nested_fn in builder.encapsulating_funcs[builder.fn_info.fitem]: |
| if isinstance(nested_fn, FuncDef): |
| # The return type is 'object' instead of an RInstance of the |
| # callable class because differently defined functions with |
| # the same name and signature across conditional blocks |
| # will generate different callable classes, so the callable |
| # class that gets instantiated must be generic. |
| builder.add_var_to_env_class( |
| nested_fn, object_rprimitive, env_for_func, reassign=False |
| ) |
| |
| builder.accept(fitem.body) |
| builder.maybe_add_implicit_return() |
| |
| if builder.fn_info.is_generator: |
| populate_switch_for_generator_class(builder) |
| |
| # Hang on to the local symbol table for a while, since we use it |
| # to calculate argument defaults below. |
| symtable = builder.symtables[-1] |
| |
| args, _, blocks, ret_type, fn_info = builder.leave() |
| |
| if fn_info.is_generator: |
| add_methods_to_generator_class( |
| builder, fn_info, sig, args, blocks, fitem.is_coroutine) |
| else: |
| func_ir, func_reg = gen_func_ir( |
| builder, args, blocks, sig, fn_info, cdef, is_singledispatch, |
| ) |
| |
| # Evaluate argument defaults in the surrounding scope, since we |
| # calculate them *once* when the function definition is evaluated. |
| calculate_arg_defaults(builder, fn_info, func_reg, symtable) |
| |
| if is_singledispatch: |
| # add the generated main singledispatch function |
| builder.functions.append(func_ir) |
| # create the dispatch function |
| assert isinstance(fitem, FuncDef) |
| return gen_dispatch_func_ir(builder, fitem, fn_info.name, name, sig) |
| |
| return func_ir, func_reg |
| |
| |
| def gen_func_ir(builder: IRBuilder, |
| args: List[Register], |
| blocks: List[BasicBlock], |
| sig: FuncSignature, |
| fn_info: FuncInfo, |
| cdef: Optional[ClassDef], |
| is_singledispatch_main_func: bool = False) -> Tuple[FuncIR, Optional[Value]]: |
| """Generate the FuncIR for a function. |
| |
| This takes the basic blocks and function info of a particular |
| function and returns the IR. If the function is nested, |
| also returns the register containing the instance of the |
| corresponding callable class. |
| """ |
| func_reg: Optional[Value] = None |
| if fn_info.is_nested or fn_info.in_non_ext: |
| func_ir = add_call_to_callable_class(builder, args, blocks, sig, fn_info) |
| add_get_to_callable_class(builder, fn_info) |
| func_reg = instantiate_callable_class(builder, fn_info) |
| else: |
| assert isinstance(fn_info.fitem, FuncDef) |
| func_decl = builder.mapper.func_to_decl[fn_info.fitem] |
| if fn_info.is_decorated or is_singledispatch_main_func: |
| class_name = None if cdef is None else cdef.name |
| func_decl = FuncDecl(fn_info.name, class_name, builder.module_name, sig, |
| func_decl.kind, |
| func_decl.is_prop_getter, func_decl.is_prop_setter) |
| func_ir = FuncIR(func_decl, args, blocks, fn_info.fitem.line, |
| traceback_name=fn_info.fitem.name) |
| else: |
| func_ir = FuncIR(func_decl, args, blocks, |
| fn_info.fitem.line, traceback_name=fn_info.fitem.name) |
| return (func_ir, func_reg) |
| |
| |
| def handle_ext_method(builder: IRBuilder, cdef: ClassDef, fdef: FuncDef) -> None: |
| # Perform the function of visit_method for methods inside extension classes. |
| name = fdef.name |
| class_ir = builder.mapper.type_to_ir[cdef.info] |
| func_ir, func_reg = gen_func_item(builder, fdef, name, builder.mapper.fdef_to_sig(fdef), cdef) |
| builder.functions.append(func_ir) |
| |
| if is_decorated(builder, fdef): |
| # Obtain the the function name in order to construct the name of the helper function. |
| _, _, name = fdef.fullname.rpartition('.') |
| # Read the PyTypeObject representing the class, get the callable object |
| # representing the non-decorated method |
| typ = builder.load_native_type_object(cdef.fullname) |
| orig_func = builder.py_get_attr(typ, name, fdef.line) |
| |
| # Decorate the non-decorated method |
| decorated_func = load_decorated_func(builder, fdef, orig_func) |
| |
| # Set the callable object representing the decorated method as an attribute of the |
| # extension class. |
| builder.call_c(py_setattr_op, |
| [typ, builder.load_str(name), decorated_func], |
| fdef.line) |
| |
| if fdef.is_property: |
| # If there is a property setter, it will be processed after the getter, |
| # We populate the optional setter field with none for now. |
| assert name not in class_ir.properties |
| class_ir.properties[name] = (func_ir, None) |
| |
| elif fdef in builder.prop_setters: |
| # The respective property getter must have been processed already |
| assert name in class_ir.properties |
| getter_ir, _ = class_ir.properties[name] |
| class_ir.properties[name] = (getter_ir, func_ir) |
| |
| class_ir.methods[func_ir.decl.name] = func_ir |
| |
| # If this overrides a parent class method with a different type, we need |
| # to generate a glue method to mediate between them. |
| for base in class_ir.mro[1:]: |
| if (name in base.method_decls and name != '__init__' |
| and not is_same_method_signature(class_ir.method_decls[name].sig, |
| base.method_decls[name].sig)): |
| |
| # TODO: Support contravariant subtyping in the input argument for |
| # property setters. Need to make a special glue method for handling this, |
| # similar to gen_glue_property. |
| |
| f = gen_glue(builder, base.method_decls[name].sig, func_ir, class_ir, base, fdef) |
| class_ir.glue_methods[(base, name)] = f |
| builder.functions.append(f) |
| |
| # If the class allows interpreted children, create glue |
| # methods that dispatch via the Python API. These will go in a |
| # "shadow vtable" that will be assigned to interpreted |
| # children. |
| if class_ir.allow_interpreted_subclasses: |
| f = gen_glue(builder, func_ir.sig, func_ir, class_ir, class_ir, fdef, do_py_ops=True) |
| class_ir.glue_methods[(class_ir, name)] = f |
| builder.functions.append(f) |
| |
| |
| def handle_non_ext_method( |
| builder: IRBuilder, non_ext: NonExtClassInfo, cdef: ClassDef, fdef: FuncDef) -> None: |
| # Perform the function of visit_method for methods inside non-extension classes. |
| name = fdef.name |
| func_ir, func_reg = gen_func_item(builder, fdef, name, builder.mapper.fdef_to_sig(fdef), cdef) |
| assert func_reg is not None |
| builder.functions.append(func_ir) |
| |
| if is_decorated(builder, fdef): |
| # The undecorated method is a generated callable class |
| orig_func = func_reg |
| func_reg = load_decorated_func(builder, fdef, orig_func) |
| |
| # TODO: Support property setters in non-extension classes |
| if fdef.is_property: |
| prop = builder.load_module_attr_by_fullname('builtins.property', fdef.line) |
| func_reg = builder.py_call(prop, [func_reg], fdef.line) |
| |
| elif builder.mapper.func_to_decl[fdef].kind == FUNC_CLASSMETHOD: |
| cls_meth = builder.load_module_attr_by_fullname('builtins.classmethod', fdef.line) |
| func_reg = builder.py_call(cls_meth, [func_reg], fdef.line) |
| |
| elif builder.mapper.func_to_decl[fdef].kind == FUNC_STATICMETHOD: |
| stat_meth = builder.load_module_attr_by_fullname( |
| 'builtins.staticmethod', fdef.line |
| ) |
| func_reg = builder.py_call(stat_meth, [func_reg], fdef.line) |
| |
| builder.add_to_non_ext_dict(non_ext, name, func_reg, fdef.line) |
| |
| |
| def calculate_arg_defaults(builder: IRBuilder, |
| fn_info: FuncInfo, |
| func_reg: Optional[Value], |
| symtable: Dict[SymbolNode, SymbolTarget]) -> None: |
| """Calculate default argument values and store them. |
| |
| They are stored in statics for top level functions and in |
| the function objects for nested functions (while constants are |
| still stored computed on demand). |
| """ |
| fitem = fn_info.fitem |
| for arg in fitem.arguments: |
| # Constant values don't get stored but just recomputed |
| if arg.initializer and not is_constant(arg.initializer): |
| value = builder.coerce( |
| builder.accept(arg.initializer), |
| symtable[arg.variable].type, |
| arg.line |
| ) |
| if not fn_info.is_nested: |
| name = fitem.fullname + '.' + arg.variable.name |
| builder.add(InitStatic(value, name, builder.module_name)) |
| else: |
| assert func_reg is not None |
| builder.add(SetAttr(func_reg, arg.variable.name, value, arg.line)) |
| |
| |
| def gen_func_ns(builder: IRBuilder) -> str: |
| """Generate a namespace for a nested function using its outer function names.""" |
| return '_'.join(info.name + ('' if not info.class_name else '_' + info.class_name) |
| for info in builder.fn_infos |
| if info.name and info.name != '<top level>') |
| |
| |
| def emit_yield(builder: IRBuilder, val: Value, line: int) -> Value: |
| retval = builder.coerce(val, builder.ret_types[-1], line) |
| |
| cls = builder.fn_info.generator_class |
| # Create a new block for the instructions immediately following the yield expression, and |
| # set the next label so that the next time '__next__' is called on the generator object, |
| # the function continues at the new block. |
| next_block = BasicBlock() |
| next_label = len(cls.continuation_blocks) |
| cls.continuation_blocks.append(next_block) |
| builder.assign(cls.next_label_target, Integer(next_label), line) |
| builder.add(Return(retval)) |
| builder.activate_block(next_block) |
| |
| add_raise_exception_blocks_to_generator_class(builder, line) |
| |
| assert cls.send_arg_reg is not None |
| return cls.send_arg_reg |
| |
| |
| def handle_yield_from_and_await(builder: IRBuilder, o: Union[YieldFromExpr, AwaitExpr]) -> Value: |
| # This is basically an implementation of the code in PEP 380. |
| |
| # TODO: do we want to use the right types here? |
| result = Register(object_rprimitive) |
| to_yield_reg = Register(object_rprimitive) |
| received_reg = Register(object_rprimitive) |
| |
| if isinstance(o, YieldFromExpr): |
| iter_val = builder.call_c(iter_op, [builder.accept(o.expr)], o.line) |
| else: |
| iter_val = builder.call_c(coro_op, [builder.accept(o.expr)], o.line) |
| |
| iter_reg = builder.maybe_spill_assignable(iter_val) |
| |
| stop_block, main_block, done_block = BasicBlock(), BasicBlock(), BasicBlock() |
| _y_init = builder.call_c(next_raw_op, [builder.read(iter_reg)], o.line) |
| builder.add(Branch(_y_init, stop_block, main_block, Branch.IS_ERROR)) |
| |
| # Try extracting a return value from a StopIteration and return it. |
| # If it wasn't, this reraises the exception. |
| builder.activate_block(stop_block) |
| builder.assign(result, builder.call_c(check_stop_op, [], o.line), o.line) |
| builder.goto(done_block) |
| |
| builder.activate_block(main_block) |
| builder.assign(to_yield_reg, _y_init, o.line) |
| |
| # OK Now the main loop! |
| loop_block = BasicBlock() |
| builder.goto_and_activate(loop_block) |
| |
| def try_body() -> None: |
| builder.assign( |
| received_reg, emit_yield(builder, builder.read(to_yield_reg), o.line), o.line |
| ) |
| |
| def except_body() -> None: |
| # The body of the except is all implemented in a C function to |
| # reduce how much code we need to generate. It returns a value |
| # indicating whether to break or yield (or raise an exception). |
| val = Register(object_rprimitive) |
| val_address = builder.add(LoadAddress(object_pointer_rprimitive, val)) |
| to_stop = builder.call_c(yield_from_except_op, |
| [builder.read(iter_reg), val_address], o.line) |
| |
| ok, stop = BasicBlock(), BasicBlock() |
| builder.add(Branch(to_stop, stop, ok, Branch.BOOL)) |
| |
| # The exception got swallowed. Continue, yielding the returned value |
| builder.activate_block(ok) |
| builder.assign(to_yield_reg, val, o.line) |
| builder.nonlocal_control[-1].gen_continue(builder, o.line) |
| |
| # The exception was a StopIteration. Stop iterating. |
| builder.activate_block(stop) |
| builder.assign(result, val, o.line) |
| builder.nonlocal_control[-1].gen_break(builder, o.line) |
| |
| def else_body() -> None: |
| # Do a next() or a .send(). It will return NULL on exception |
| # but it won't automatically propagate. |
| _y = builder.call_c( |
| send_op, [builder.read(iter_reg), builder.read(received_reg)], o.line |
| ) |
| ok, stop = BasicBlock(), BasicBlock() |
| builder.add(Branch(_y, stop, ok, Branch.IS_ERROR)) |
| |
| # Everything's fine. Yield it. |
| builder.activate_block(ok) |
| builder.assign(to_yield_reg, _y, o.line) |
| builder.nonlocal_control[-1].gen_continue(builder, o.line) |
| |
| # Try extracting a return value from a StopIteration and return it. |
| # If it wasn't, this rereaises the exception. |
| builder.activate_block(stop) |
| builder.assign(result, builder.call_c(check_stop_op, [], o.line), o.line) |
| builder.nonlocal_control[-1].gen_break(builder, o.line) |
| |
| builder.push_loop_stack(loop_block, done_block) |
| transform_try_except( |
| builder, try_body, [(None, None, except_body)], else_body, o.line |
| ) |
| builder.pop_loop_stack() |
| |
| builder.goto_and_activate(done_block) |
| return builder.read(result) |
| |
| |
| def load_decorated_func(builder: IRBuilder, fdef: FuncDef, orig_func_reg: Value) -> Value: |
| """Apply decorators to a function. |
| |
| Given a decorated FuncDef and an instance of the callable class |
| representing that FuncDef, apply the corresponding decorator |
| functions on that decorated FuncDef and return the decorated |
| function. |
| """ |
| if not is_decorated(builder, fdef): |
| # If there are no decorators associated with the function, then just return the |
| # original function. |
| return orig_func_reg |
| |
| decorators = builder.fdefs_to_decorators[fdef] |
| func_reg = orig_func_reg |
| for d in reversed(decorators): |
| decorator = d.accept(builder.visitor) |
| assert isinstance(decorator, Value) |
| func_reg = builder.py_call(decorator, [func_reg], func_reg.line) |
| return func_reg |
| |
| |
| def is_decorated(builder: IRBuilder, fdef: FuncDef) -> bool: |
| return fdef in builder.fdefs_to_decorators |
| |
| |
| def gen_glue(builder: IRBuilder, sig: FuncSignature, target: FuncIR, |
| cls: ClassIR, base: ClassIR, fdef: FuncItem, |
| *, |
| do_py_ops: bool = False |
| ) -> FuncIR: |
| """Generate glue methods that mediate between different method types in subclasses. |
| |
| Works on both properties and methods. See gen_glue_methods below |
| for more details. |
| |
| If do_py_ops is True, then the glue methods should use generic |
| C API operations instead of direct calls, to enable generating |
| "shadow" glue methods that work with interpreted subclasses. |
| """ |
| if fdef.is_property: |
| return gen_glue_property(builder, sig, target, cls, base, fdef.line, do_py_ops) |
| else: |
| return gen_glue_method(builder, sig, target, cls, base, fdef.line, do_py_ops) |
| |
| |
| class ArgInfo(NamedTuple): |
| args: List[Value] |
| arg_names: List[Optional[str]] |
| arg_kinds: List[ArgKind] |
| |
| |
| def get_args(builder: IRBuilder, rt_args: Sequence[RuntimeArg], line: int) -> ArgInfo: |
| # 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.is_named() or (arg.kind.is_optional() and not arg.pos_only) else None |
| for arg in rt_args] |
| arg_kinds = [arg.kind for arg in rt_args] |
| return ArgInfo(args, arg_names, arg_kinds) |
| |
| |
| 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 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)) |
| self_target = builder.add_self_to_env(cls) |
| arg = builder.read(self_target, 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)) |
| |
| args, _, blocks, return_type, _ = builder.leave() |
| return FuncIR( |
| FuncDecl(target.name + '__' + base.name + '_glue', |
| cls.name, builder.module_name, FuncSignature([rt_arg], return_type)), |
| args, blocks) |
| |
| |
| def get_func_target(builder: IRBuilder, fdef: FuncDef) -> AssignmentTarget: |
| """Given a FuncDef, return the target for the instance of its callable class. |
| |
| If the function was not already defined somewhere, then define it |
| and add it to the current environment. |
| """ |
| if fdef.original_def: |
| # Get the target associated with the previously defined FuncDef. |
| return builder.lookup(fdef.original_def) |
| |
| if builder.fn_info.is_generator or builder.fn_info.contains_nested: |
| return builder.lookup(fdef) |
| |
| return builder.add_local_reg(fdef, object_rprimitive) |
| |
| |
| def load_type(builder: IRBuilder, typ: TypeInfo, line: int) -> Value: |
| if typ in builder.mapper.type_to_ir: |
| class_ir = builder.mapper.type_to_ir[typ] |
| class_obj = builder.builder.get_native_type(class_ir) |
| elif typ.fullname in builtin_names: |
| builtin_addr_type, src = builtin_names[typ.fullname] |
| class_obj = builder.add(LoadAddress(builtin_addr_type, src, line)) |
| else: |
| class_obj = builder.load_global_str(typ.name, line) |
| |
| return class_obj |
| |
| |
| def load_func(builder: IRBuilder, func_name: str, fullname: Optional[str], line: int) -> Value: |
| if fullname is not None and not fullname.startswith(builder.current_module): |
| # we're calling a function in a different module |
| |
| # We can't use load_module_attr_by_fullname here because we need to load the function using |
| # func_name, not the name specified by fullname (which can be different for underscore |
| # function) |
| module = fullname.rsplit('.')[0] |
| loaded_module = builder.load_module(module) |
| |
| func = builder.py_get_attr(loaded_module, func_name, line) |
| else: |
| func = builder.load_global_str(func_name, line) |
| return func |
| |
| |
| def generate_singledispatch_dispatch_function( |
| builder: IRBuilder, |
| main_singledispatch_function_name: str, |
| fitem: FuncDef, |
| ) -> None: |
| line = fitem.line |
| current_func_decl = builder.mapper.func_to_decl[fitem] |
| arg_info = get_args(builder, current_func_decl.sig.args, line) |
| |
| dispatch_func_obj = builder.self() |
| |
| arg_type = builder.builder.get_type_of_obj(arg_info.args[0], line) |
| dispatch_cache = builder.builder.get_attr( |
| dispatch_func_obj, 'dispatch_cache', dict_rprimitive, line |
| ) |
| call_find_impl, use_cache, call_func = BasicBlock(), BasicBlock(), BasicBlock() |
| get_result = builder.call_c(dict_get_method_with_none, [dispatch_cache, arg_type], line) |
| is_not_none = builder.translate_is_op(get_result, builder.none_object(), 'is not', line) |
| impl_to_use = Register(object_rprimitive) |
| builder.add_bool_branch(is_not_none, use_cache, call_find_impl) |
| |
| builder.activate_block(use_cache) |
| builder.assign(impl_to_use, get_result, line) |
| builder.goto(call_func) |
| |
| builder.activate_block(call_find_impl) |
| find_impl = builder.load_module_attr_by_fullname('functools._find_impl', line) |
| registry = load_singledispatch_registry(builder, dispatch_func_obj, line) |
| uncached_impl = builder.py_call(find_impl, [arg_type, registry], line) |
| builder.call_c(dict_set_item_op, [dispatch_cache, arg_type, uncached_impl], line) |
| builder.assign(impl_to_use, uncached_impl, line) |
| builder.goto(call_func) |
| |
| builder.activate_block(call_func) |
| gen_calls_to_correct_impl(builder, impl_to_use, arg_info, fitem, line) |
| |
| |
| def gen_calls_to_correct_impl( |
| builder: IRBuilder, |
| impl_to_use: Value, |
| arg_info: ArgInfo, |
| fitem: FuncDef, |
| line: int, |
| ) -> None: |
| current_func_decl = builder.mapper.func_to_decl[fitem] |
| |
| def gen_native_func_call_and_return(fdef: FuncDef) -> None: |
| func_decl = builder.mapper.func_to_decl[fdef] |
| ret_val = builder.builder.call( |
| func_decl, arg_info.args, arg_info.arg_kinds, arg_info.arg_names, line |
| ) |
| coerced = builder.coerce(ret_val, current_func_decl.sig.ret_type, line) |
| builder.add(Return(coerced)) |
| |
| typ, src = builtin_names['builtins.int'] |
| int_type_obj = builder.add(LoadAddress(typ, src, line)) |
| is_int = builder.builder.type_is_op(impl_to_use, int_type_obj, line) |
| |
| native_call, non_native_call = BasicBlock(), BasicBlock() |
| builder.add_bool_branch(is_int, native_call, non_native_call) |
| builder.activate_block(native_call) |
| |
| passed_id = builder.add(Unbox(impl_to_use, int_rprimitive, line)) |
| |
| native_ids = get_native_impl_ids(builder, fitem) |
| for impl, i in native_ids.items(): |
| call_impl, next_impl = BasicBlock(), BasicBlock() |
| |
| current_id = builder.load_int(i) |
| builder.builder.compare_tagged_condition( |
| passed_id, |
| current_id, |
| '==', |
| call_impl, |
| next_impl, |
| line, |
| ) |
| |
| # Call the registered implementation |
| builder.activate_block(call_impl) |
| |
| gen_native_func_call_and_return(impl) |
| builder.activate_block(next_impl) |
| |
| # We've already handled all the possible integer IDs, so we should never get here |
| builder.add(Unreachable()) |
| |
| builder.activate_block(non_native_call) |
| ret_val = builder.py_call( |
| impl_to_use, arg_info.args, line, arg_info.arg_kinds, arg_info.arg_names |
| ) |
| coerced = builder.coerce(ret_val, current_func_decl.sig.ret_type, line) |
| builder.add(Return(coerced)) |
| |
| |
| def gen_dispatch_func_ir( |
| builder: IRBuilder, |
| fitem: FuncDef, |
| main_func_name: str, |
| dispatch_name: str, |
| sig: FuncSignature, |
| ) -> Tuple[FuncIR, Value]: |
| """Create a dispatch function (a function that checks the first argument type and dispatches |
| to the correct implementation) |
| """ |
| builder.enter(FuncInfo(fitem, dispatch_name)) |
| setup_callable_class(builder) |
| builder.fn_info.callable_class.ir.attributes['registry'] = dict_rprimitive |
| builder.fn_info.callable_class.ir.attributes['dispatch_cache'] = dict_rprimitive |
| builder.fn_info.callable_class.ir.has_dict = True |
| builder.fn_info.callable_class.ir.needs_getseters = True |
| generate_singledispatch_callable_class_ctor(builder) |
| |
| generate_singledispatch_dispatch_function(builder, main_func_name, fitem) |
| args, _, blocks, _, fn_info = builder.leave() |
| dispatch_callable_class = add_call_to_callable_class(builder, args, blocks, sig, fn_info) |
| builder.functions.append(dispatch_callable_class) |
| add_get_to_callable_class(builder, fn_info) |
| add_register_method_to_callable_class(builder, fn_info) |
| func_reg = instantiate_callable_class(builder, fn_info) |
| dispatch_func_ir = generate_dispatch_glue_native_function( |
| builder, fitem, dispatch_callable_class.decl, dispatch_name |
| ) |
| |
| return dispatch_func_ir, func_reg |
| |
| |
| def generate_dispatch_glue_native_function( |
| builder: IRBuilder, |
| fitem: FuncDef, |
| callable_class_decl: FuncDecl, |
| dispatch_name: str, |
| ) -> FuncIR: |
| line = fitem.line |
| builder.enter() |
| # We store the callable class in the globals dict for this function |
| callable_class = builder.load_global_str(dispatch_name, line) |
| decl = builder.mapper.func_to_decl[fitem] |
| arg_info = get_args(builder, decl.sig.args, line) |
| args = [callable_class] + arg_info.args |
| arg_kinds = [ArgKind.ARG_POS] + arg_info.arg_kinds |
| arg_names = arg_info.arg_names |
| arg_names.insert(0, 'self') |
| ret_val = builder.builder.call(callable_class_decl, args, arg_kinds, arg_names, line) |
| builder.add(Return(ret_val)) |
| arg_regs, _, blocks, _, fn_info = builder.leave() |
| return FuncIR(decl, arg_regs, blocks) |
| |
| |
| def generate_singledispatch_callable_class_ctor(builder: IRBuilder) -> None: |
| """Create an __init__ that sets registry and dispatch_cache to empty dicts""" |
| line = -1 |
| class_ir = builder.fn_info.callable_class.ir |
| with builder.enter_method(class_ir, '__init__', bool_rprimitive): |
| empty_dict = builder.call_c(dict_new_op, [], line) |
| builder.add(SetAttr(builder.self(), 'registry', empty_dict, line)) |
| cache_dict = builder.call_c(dict_new_op, [], line) |
| dispatch_cache_str = builder.load_str('dispatch_cache') |
| # use the py_setattr_op instead of SetAttr so that it also gets added to our __dict__ |
| builder.call_c(py_setattr_op, [builder.self(), dispatch_cache_str, cache_dict], line) |
| # the generated C code seems to expect that __init__ returns a char, so just return 1 |
| builder.add(Return(Integer(1, bool_rprimitive, line), line)) |
| |
| |
| def add_register_method_to_callable_class(builder: IRBuilder, fn_info: FuncInfo) -> None: |
| line = -1 |
| with builder.enter_method(fn_info.callable_class.ir, 'register', object_rprimitive): |
| cls_arg = builder.add_argument('cls', object_rprimitive) |
| func_arg = builder.add_argument('func', object_rprimitive, ArgKind.ARG_OPT) |
| ret_val = builder.call_c(register_function, [builder.self(), cls_arg, func_arg], line) |
| builder.add(Return(ret_val, line)) |
| |
| |
| def load_singledispatch_registry(builder: IRBuilder, dispatch_func_obj: Value, line: int) -> Value: |
| return builder.builder.get_attr(dispatch_func_obj, 'registry', dict_rprimitive, line) |
| |
| |
| def singledispatch_main_func_name(orig_name: str) -> str: |
| return '__mypyc_singledispatch_main_function_{}__'.format(orig_name) |
| |
| |
| def get_registry_identifier(fitem: FuncDef) -> str: |
| return f'__mypyc_singledispatch_registry_{fitem.fullname}__' |
| |
| |
| def maybe_insert_into_registry_dict(builder: IRBuilder, fitem: FuncDef) -> None: |
| line = fitem.line |
| is_singledispatch_main_func = fitem in builder.singledispatch_impls |
| # dict of singledispatch_func to list of register_types (fitem is the function to register) |
| to_register: DefaultDict[FuncDef, List[TypeInfo]] = defaultdict(list) |
| for main_func, impls in builder.singledispatch_impls.items(): |
| for dispatch_type, impl in impls: |
| if fitem == impl: |
| to_register[main_func].append(dispatch_type) |
| |
| if not to_register and not is_singledispatch_main_func: |
| return |
| |
| if is_singledispatch_main_func: |
| main_func_name = singledispatch_main_func_name(fitem.name) |
| main_func_obj = load_func(builder, main_func_name, fitem.fullname, line) |
| |
| loaded_object_type = builder.load_module_attr_by_fullname('builtins.object', line) |
| registry_dict = builder.builder.make_dict([(loaded_object_type, main_func_obj)], line) |
| |
| dispatch_func_obj = builder.load_global_str(fitem.name, line) |
| builder.call_c( |
| py_setattr_op, [dispatch_func_obj, builder.load_str('registry'), registry_dict], line |
| ) |
| |
| for singledispatch_func, types in to_register.items(): |
| # TODO: avoid recomputing the native IDs for all the functions every time we find a new |
| # function |
| native_ids = get_native_impl_ids(builder, singledispatch_func) |
| if fitem not in native_ids: |
| to_insert = load_func(builder, fitem.name, fitem.fullname, line) |
| else: |
| current_id = native_ids[fitem] |
| load_literal = LoadLiteral(current_id, object_rprimitive) |
| to_insert = builder.add(load_literal) |
| # TODO: avoid reloading the registry here if we just created it |
| dispatch_func_obj = load_func( |
| builder, singledispatch_func.name, singledispatch_func.fullname, line |
| ) |
| registry = load_singledispatch_registry(builder, dispatch_func_obj, line) |
| for typ in types: |
| loaded_type = load_type(builder, typ, line) |
| builder.call_c(dict_set_item_op, [registry, loaded_type, to_insert], line) |
| dispatch_cache = builder.builder.get_attr( |
| dispatch_func_obj, 'dispatch_cache', dict_rprimitive, line |
| ) |
| builder.gen_method_call(dispatch_cache, 'clear', [], None, line) |
| |
| |
| def get_native_impl_ids(builder: IRBuilder, singledispatch_func: FuncDef) -> Dict[FuncDef, int]: |
| """Return a dict of registered implementation to native implementation ID for all |
| implementations |
| """ |
| impls = builder.singledispatch_impls[singledispatch_func] |
| return {impl: i for i, (typ, impl) in enumerate(impls) if not is_decorated(builder, impl)} |