callable_class.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. """Generate a class that represents a nested function.
  2. The class defines __call__ for calling the function and allows access to
  3. non-local variables defined in outer scopes.
  4. """
  5. from __future__ import annotations
  6. from mypyc.common import ENV_ATTR_NAME, SELF_NAME
  7. from mypyc.ir.class_ir import ClassIR
  8. from mypyc.ir.func_ir import FuncDecl, FuncIR, FuncSignature, RuntimeArg
  9. from mypyc.ir.ops import BasicBlock, Call, Register, Return, SetAttr, Value
  10. from mypyc.ir.rtypes import RInstance, object_rprimitive
  11. from mypyc.irbuild.builder import IRBuilder
  12. from mypyc.irbuild.context import FuncInfo, ImplicitClass
  13. from mypyc.primitives.misc_ops import method_new_op
  14. def setup_callable_class(builder: IRBuilder) -> None:
  15. """Generate an (incomplete) callable class representing function.
  16. This can be a nested function or a function within a non-extension
  17. class. Also set up the 'self' variable for that class.
  18. This takes the most recently visited function and returns a
  19. ClassIR to represent that function. Each callable class contains
  20. an environment attribute which points to another ClassIR
  21. representing the environment class where some of its variables can
  22. be accessed.
  23. Note that some methods, such as '__call__', are not yet
  24. created here. Use additional functions, such as
  25. add_call_to_callable_class(), to add them.
  26. Return a newly constructed ClassIR representing the callable
  27. class for the nested function.
  28. """
  29. # Check to see that the name has not already been taken. If so,
  30. # rename the class. We allow multiple uses of the same function
  31. # name because this is valid in if-else blocks. Example:
  32. #
  33. # if True:
  34. # def foo(): ----> foo_obj()
  35. # return True
  36. # else:
  37. # def foo(): ----> foo_obj_0()
  38. # return False
  39. name = base_name = f"{builder.fn_info.namespaced_name()}_obj"
  40. count = 0
  41. while name in builder.callable_class_names:
  42. name = base_name + "_" + str(count)
  43. count += 1
  44. builder.callable_class_names.add(name)
  45. # Define the actual callable class ClassIR, and set its
  46. # environment to point at the previously defined environment
  47. # class.
  48. callable_class_ir = ClassIR(name, builder.module_name, is_generated=True)
  49. # The functools @wraps decorator attempts to call setattr on
  50. # nested functions, so we create a dict for these nested
  51. # functions.
  52. # https://github.com/python/cpython/blob/3.7/Lib/functools.py#L58
  53. if builder.fn_info.is_nested:
  54. callable_class_ir.has_dict = True
  55. # If the enclosing class doesn't contain nested (which will happen if
  56. # this is a toplevel lambda), don't set up an environment.
  57. if builder.fn_infos[-2].contains_nested:
  58. callable_class_ir.attributes[ENV_ATTR_NAME] = RInstance(builder.fn_infos[-2].env_class)
  59. callable_class_ir.mro = [callable_class_ir]
  60. builder.fn_info.callable_class = ImplicitClass(callable_class_ir)
  61. builder.classes.append(callable_class_ir)
  62. # Add a 'self' variable to the environment of the callable class,
  63. # and store that variable in a register to be accessed later.
  64. self_target = builder.add_self_to_env(callable_class_ir)
  65. builder.fn_info.callable_class.self_reg = builder.read(self_target, builder.fn_info.fitem.line)
  66. def add_call_to_callable_class(
  67. builder: IRBuilder,
  68. args: list[Register],
  69. blocks: list[BasicBlock],
  70. sig: FuncSignature,
  71. fn_info: FuncInfo,
  72. ) -> FuncIR:
  73. """Generate a '__call__' method for a callable class representing a nested function.
  74. This takes the blocks and signature associated with a function
  75. definition and uses those to build the '__call__' method of a
  76. given callable class, used to represent that function.
  77. """
  78. # Since we create a method, we also add a 'self' parameter.
  79. nargs = len(sig.args) - sig.num_bitmap_args
  80. sig = FuncSignature(
  81. (RuntimeArg(SELF_NAME, object_rprimitive),) + sig.args[:nargs], sig.ret_type
  82. )
  83. call_fn_decl = FuncDecl("__call__", fn_info.callable_class.ir.name, builder.module_name, sig)
  84. call_fn_ir = FuncIR(
  85. call_fn_decl, args, blocks, fn_info.fitem.line, traceback_name=fn_info.fitem.name
  86. )
  87. fn_info.callable_class.ir.methods["__call__"] = call_fn_ir
  88. fn_info.callable_class.ir.method_decls["__call__"] = call_fn_decl
  89. return call_fn_ir
  90. def add_get_to_callable_class(builder: IRBuilder, fn_info: FuncInfo) -> None:
  91. """Generate the '__get__' method for a callable class."""
  92. line = fn_info.fitem.line
  93. with builder.enter_method(
  94. fn_info.callable_class.ir,
  95. "__get__",
  96. object_rprimitive,
  97. fn_info,
  98. self_type=object_rprimitive,
  99. ):
  100. instance = builder.add_argument("instance", object_rprimitive)
  101. builder.add_argument("owner", object_rprimitive)
  102. # If accessed through the class, just return the callable
  103. # object. If accessed through an object, create a new bound
  104. # instance method object.
  105. instance_block, class_block = BasicBlock(), BasicBlock()
  106. comparison = builder.translate_is_op(
  107. builder.read(instance), builder.none_object(), "is", line
  108. )
  109. builder.add_bool_branch(comparison, class_block, instance_block)
  110. builder.activate_block(class_block)
  111. builder.add(Return(builder.self()))
  112. builder.activate_block(instance_block)
  113. builder.add(
  114. Return(builder.call_c(method_new_op, [builder.self(), builder.read(instance)], line))
  115. )
  116. def instantiate_callable_class(builder: IRBuilder, fn_info: FuncInfo) -> Value:
  117. """Create an instance of a callable class for a function.
  118. Calls to the function will actually call this instance.
  119. Note that fn_info refers to the function being assigned, whereas
  120. builder.fn_info refers to the function encapsulating the function
  121. being turned into a callable class.
  122. """
  123. fitem = fn_info.fitem
  124. func_reg = builder.add(Call(fn_info.callable_class.ir.ctor, [], fitem.line))
  125. # Set the environment attribute of the callable class to point at
  126. # the environment class defined in the callable class' immediate
  127. # outer scope. Note that there are three possible environment
  128. # class registers we may use. This depends on what the encapsulating
  129. # (parent) function is:
  130. #
  131. # - A nested function: the callable class is instantiated
  132. # from the current callable class' '__call__' function, and hence
  133. # the callable class' environment register is used.
  134. # - A generator function: the callable class is instantiated
  135. # from the '__next__' method of the generator class, and hence the
  136. # environment of the generator class is used.
  137. # - Regular function: we use the environment of the original function.
  138. curr_env_reg = None
  139. if builder.fn_info.is_generator:
  140. curr_env_reg = builder.fn_info.generator_class.curr_env_reg
  141. elif builder.fn_info.is_nested:
  142. curr_env_reg = builder.fn_info.callable_class.curr_env_reg
  143. elif builder.fn_info.contains_nested:
  144. curr_env_reg = builder.fn_info.curr_env_reg
  145. if curr_env_reg:
  146. builder.add(SetAttr(func_reg, ENV_ATTR_NAME, curr_env_reg, fitem.line))
  147. return func_reg