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) ) if dec.func in builder.nested_fitems: assert func_reg is not None 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 else: # Obtain the the function name in order to construct the name of the helper function. name = dec.func.fullname.split('.')[-1] helper_name = decorator_helper_name(name) # Load the callable object representing the non-decorated function, and decorate it. orig_func = builder.load_global_str(helper_name, dec.line) decorated_func = load_decorated_func(builder, dec.func, orig_func) # Set the callable object representing the decorated function as a global. builder.call_c(dict_set_item_op, [builder.load_globals_dict(), builder.load_static_unicode(dec.func.name), decorated_func], decorated_func.line) builder.functions.append(func_ir)
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) ) if dec.func in builder.nested_fitems: assert func_reg is not None 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] helper_name = decorator_helper_name(name) # Load the callable object representing the non-decorated function, and decorate it. orig_func = builder.load_global_str(helper_name, dec.line) decorated_func = load_decorated_func(builder, dec.func, orig_func) # 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) builder.functions.append(func_ir)
def __init__(self, fitem: FuncItem = INVALID_FUNC_DEF, name: str = '', class_name: Optional[str] = None, namespace: str = '', is_nested: bool = False, contains_nested: bool = False, is_decorated: bool = False, in_non_ext: bool = False) -> None: self.fitem = fitem self.name = name if not is_decorated else decorator_helper_name(name) self.class_name = class_name self.ns = namespace # Callable classes implement the '__call__' method, and are used to represent functions # that are nested inside of other functions. self._callable_class = None # type: Optional[ImplicitClass] # Environment classes are ClassIR instances that contain attributes representing the # variables in the environment of the function they correspond to. Environment classes are # generated for functions that contain nested functions. self._env_class = None # type: Optional[ClassIR] # Generator classes implement the '__next__' method, and are used to represent generators # returned by generator functions. self._generator_class = None # type: Optional[GeneratorClass] # Environment class registers are the local registers associated with instances of an # environment class, used for getting and setting attributes. curr_env_reg is the register # associated with the current environment. self._curr_env_reg = None # type: Optional[Value] # These are flags denoting whether a given function is nested, contains a nested function, # is decorated, or is within a non-extension class. self.is_nested = is_nested self.contains_nested = contains_nested self.is_decorated = is_decorated self.in_non_ext = in_non_ext
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('.') helper_name = decorator_helper_name(name) # 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, helper_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.primitive_op(py_setattr_op, [ typ, builder.load_static_unicode(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 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 = '__mypyc_singledispatch_main_function_{}__'.format(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) # 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) # 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) dispatch_name = decorator_helper_name(name) if is_decorated else name dispatch_func_ir = gen_dispatch_func_ir(builder, fitem, fn_info.name, dispatch_name, sig) return dispatch_func_ir, None return (func_ir, func_reg)