Skip to content

Commit 320ea65

Browse files
authored
[mypyc] Refactor: adjust generator return types in a later pass (#20043)
Move the inference of a more precise generator return type to a later pass. This way we have access to full class inheritance hierarchies. This is in preparation to fixing mypyc/mypyc#1141.
1 parent 826e0ad commit 320ea65

File tree

3 files changed

+51
-39
lines changed

3 files changed

+51
-39
lines changed

mypyc/irbuild/main.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@ def f(x: int) -> int:
3838
from mypyc.irbuild.mapper import Mapper
3939
from mypyc.irbuild.prebuildvisitor import PreBuildVisitor
4040
from mypyc.irbuild.prepare import (
41+
adjust_generator_classes_of_methods,
4142
build_type_map,
42-
create_generator_class_if_needed,
43+
create_generator_class_for_func,
4344
find_singledispatch_register_impls,
4445
)
4546
from mypyc.irbuild.visitor import IRBuilderVisitor
@@ -68,6 +69,7 @@ def build_ir(
6869
"""
6970

7071
build_type_map(mapper, modules, graph, types, options, errors)
72+
adjust_generator_classes_of_methods(mapper)
7173
singledispatch_info = find_singledispatch_register_impls(modules, errors)
7274

7375
result: ModuleIRs = {}
@@ -87,9 +89,10 @@ def build_ir(
8789
if isinstance(fdef, FuncDef):
8890
# Make generator class name sufficiently unique.
8991
suffix = f"___{fdef.line}"
90-
create_generator_class_if_needed(
91-
module.fullname, None, fdef, mapper, name_suffix=suffix
92-
)
92+
if fdef.is_coroutine or fdef.is_generator:
93+
create_generator_class_for_func(
94+
module.fullname, None, fdef, mapper, name_suffix=suffix
95+
)
9396

9497
# Construct and configure builder objects (cyclic runtime dependency).
9598
visitor = IRBuilderVisitor()

mypyc/irbuild/mapper.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -180,14 +180,7 @@ def fdef_to_sig(self, fdef: FuncDef, strict_dunders_typing: bool) -> FuncSignatu
180180
for typ, kind in zip(fdef.type.arg_types, fdef.type.arg_kinds)
181181
]
182182
arg_pos_onlys = [name is None for name in fdef.type.arg_names]
183-
# TODO: We could probably support decorators sometimes (static and class method?)
184-
if (fdef.is_coroutine or fdef.is_generator) and not fdef.is_decorated:
185-
# Give a more precise type for generators, so that we can optimize
186-
# code that uses them. They return a generator object, which has a
187-
# specific class. Without this, the type would have to be 'object'.
188-
ret: RType = RInstance(self.fdef_to_generator[fdef])
189-
else:
190-
ret = self.type_to_rtype(fdef.type.ret_type)
183+
ret = self.type_to_rtype(fdef.type.ret_type)
191184
else:
192185
# Handle unannotated functions
193186
arg_types = [object_rprimitive for _ in fdef.arguments]

mypyc/irbuild/prepare.py

Lines changed: 43 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -196,8 +196,6 @@ def prepare_func_def(
196196
mapper: Mapper,
197197
options: CompilerOptions,
198198
) -> FuncDecl:
199-
create_generator_class_if_needed(module_name, class_name, fdef, mapper)
200-
201199
kind = (
202200
FUNC_CLASSMETHOD
203201
if fdef.is_class
@@ -209,38 +207,37 @@ def prepare_func_def(
209207
return decl
210208

211209

212-
def create_generator_class_if_needed(
210+
def create_generator_class_for_func(
213211
module_name: str, class_name: str | None, fdef: FuncDef, mapper: Mapper, name_suffix: str = ""
214-
) -> None:
215-
"""If function is a generator/async function, declare a generator class.
212+
) -> ClassIR:
213+
"""For a generator/async function, declare a generator class.
216214
217215
Each generator and async function gets a dedicated class that implements the
218216
generator protocol with generated methods.
219217
"""
220-
if fdef.is_coroutine or fdef.is_generator:
221-
name = "_".join(x for x in [fdef.name, class_name] if x) + "_gen" + name_suffix
222-
cir = ClassIR(name, module_name, is_generated=True, is_final_class=True)
223-
cir.reuse_freed_instance = True
224-
mapper.fdef_to_generator[fdef] = cir
218+
assert fdef.is_coroutine or fdef.is_generator
219+
name = "_".join(x for x in [fdef.name, class_name] if x) + "_gen" + name_suffix
220+
cir = ClassIR(name, module_name, is_generated=True, is_final_class=True)
221+
cir.reuse_freed_instance = True
222+
mapper.fdef_to_generator[fdef] = cir
225223

226-
helper_sig = FuncSignature(
227-
(
228-
RuntimeArg(SELF_NAME, object_rprimitive),
229-
RuntimeArg("type", object_rprimitive),
230-
RuntimeArg("value", object_rprimitive),
231-
RuntimeArg("traceback", object_rprimitive),
232-
RuntimeArg("arg", object_rprimitive),
233-
# If non-NULL, used to store return value instead of raising StopIteration(retv)
234-
RuntimeArg("stop_iter_ptr", object_pointer_rprimitive),
235-
),
236-
object_rprimitive,
237-
)
224+
helper_sig = FuncSignature(
225+
(
226+
RuntimeArg(SELF_NAME, object_rprimitive),
227+
RuntimeArg("type", object_rprimitive),
228+
RuntimeArg("value", object_rprimitive),
229+
RuntimeArg("traceback", object_rprimitive),
230+
RuntimeArg("arg", object_rprimitive),
231+
# If non-NULL, used to store return value instead of raising StopIteration(retv)
232+
RuntimeArg("stop_iter_ptr", object_pointer_rprimitive),
233+
),
234+
object_rprimitive,
235+
)
238236

239-
# The implementation of most generator functionality is behind this magic method.
240-
helper_fn_decl = FuncDecl(
241-
GENERATOR_HELPER_NAME, name, module_name, helper_sig, internal=True
242-
)
243-
cir.method_decls[helper_fn_decl.name] = helper_fn_decl
237+
# The implementation of most generator functionality is behind this magic method.
238+
helper_fn_decl = FuncDecl(GENERATOR_HELPER_NAME, name, module_name, helper_sig, internal=True)
239+
cir.method_decls[helper_fn_decl.name] = helper_fn_decl
240+
return cir
244241

245242

246243
def prepare_method_def(
@@ -811,3 +808,22 @@ def registered_impl_from_possible_register_call(
811808
if isinstance(node, Decorator):
812809
return RegisteredImpl(node.func, dispatch_type)
813810
return None
811+
812+
813+
def adjust_generator_classes_of_methods(mapper: Mapper) -> None:
814+
"""Make optimizations and adjustments to generated generator classes of methods.
815+
816+
This is a separate pass after type map has been built, since we need all classes
817+
to be processed to analyze class hierarchies.
818+
"""
819+
for fdef, ir in mapper.func_to_decl.items():
820+
if isinstance(fdef, FuncDef) and (fdef.is_coroutine or fdef.is_generator):
821+
gen_ir = create_generator_class_for_func(ir.module_name, ir.class_name, fdef, mapper)
822+
# TODO: We could probably support decorators sometimes (static and class method?)
823+
if not fdef.is_decorated:
824+
# Give a more precise type for generators, so that we can optimize
825+
# code that uses them. They return a generator object, which has a
826+
# specific class. Without this, the type would have to be 'object'.
827+
ir.sig.ret_type = RInstance(gen_ir)
828+
if ir.bound_sig:
829+
ir.bound_sig.ret_type = RInstance(gen_ir)

0 commit comments

Comments
 (0)