Skip to content

Commit b05d220

Browse files
Merge branch 'master' into patch-3
2 parents 67bcf4b + 4934c2b commit b05d220

File tree

13 files changed

+195
-33
lines changed

13 files changed

+195
-33
lines changed

mypyc/codegen/emitclass.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -304,9 +304,6 @@ def emit_line() -> None:
304304
emit_line()
305305
generate_dealloc_for_class(cl, dealloc_name, clear_name, bool(del_method), emitter)
306306
emit_line()
307-
if del_method:
308-
generate_finalize_for_class(del_method, finalize_name, emitter)
309-
emit_line()
310307

311308
if cl.allow_interpreted_subclasses:
312309
shadow_vtable_name: str | None = generate_vtables(
@@ -317,6 +314,9 @@ def emit_line() -> None:
317314
shadow_vtable_name = None
318315
vtable_name = generate_vtables(cl, vtable_setup_name, vtable_name, emitter, shadow=False)
319316
emit_line()
317+
if del_method:
318+
generate_finalize_for_class(del_method, finalize_name, emitter)
319+
emit_line()
320320
if needs_getseters:
321321
generate_getseter_declarations(cl, emitter)
322322
emit_line()

mypyc/codegen/emitmodule.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,10 @@ def compile_ir_to_c(
306306
for source in sources
307307
}
308308

309-
names = NameGenerator([[source.module for source in sources] for sources, _ in groups])
309+
names = NameGenerator(
310+
[[source.module for source in sources] for sources, _ in groups],
311+
separate=compiler_options.separate,
312+
)
310313

311314
# Generate C code for each compilation group. Each group will be
312315
# compiled into a separate extension module.

mypyc/irbuild/context.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,11 @@ def curr_env_reg(self) -> Value:
9595
assert self._curr_env_reg is not None
9696
return self._curr_env_reg
9797

98+
def can_merge_generator_and_env_classes(self) -> bool:
99+
# In simple cases we can place the environment into the generator class,
100+
# instead of having two separate classes.
101+
return self.is_generator and not self.is_nested and not self.contains_nested
102+
98103

99104
class ImplicitClass:
100105
"""Contains information regarding implicitly generated classes.

mypyc/irbuild/env_class.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ class is generated, the function environment has not yet been
5858

5959
def finalize_env_class(builder: IRBuilder) -> None:
6060
"""Generate, instantiate, and set up the environment of an environment class."""
61-
instantiate_env_class(builder)
61+
if not builder.fn_info.can_merge_generator_and_env_classes():
62+
instantiate_env_class(builder)
6263

6364
# Iterate through the function arguments and replace local definitions (using registers)
6465
# that were previously added to the environment with references to the function's

mypyc/irbuild/function.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,9 @@ def c() -> None:
243243
# are free in their nested functions. Generator functions need an environment class to
244244
# store a variable denoting the next instruction to be executed when the __next__ function
245245
# is called, along with all the variables inside the function itself.
246-
if contains_nested or is_generator:
246+
if contains_nested or (
247+
is_generator and not builder.fn_info.can_merge_generator_and_env_classes()
248+
):
247249
setup_env_class(builder)
248250

249251
if is_nested or in_non_ext:

mypyc/irbuild/generator.py

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,14 @@ def gen_generator_func(
6464
setup_generator_class(builder)
6565
load_env_registers(builder)
6666
gen_arg_defaults(builder)
67-
finalize_env_class(builder)
68-
builder.add(Return(instantiate_generator_class(builder)))
67+
if builder.fn_info.can_merge_generator_and_env_classes():
68+
gen = instantiate_generator_class(builder)
69+
builder.fn_info._curr_env_reg = gen
70+
finalize_env_class(builder)
71+
else:
72+
finalize_env_class(builder)
73+
gen = instantiate_generator_class(builder)
74+
builder.add(Return(gen))
6975

7076
args, _, blocks, ret_type, fn_info = builder.leave()
7177
func_ir, func_reg = gen_func_ir(args, blocks, fn_info)
@@ -122,30 +128,38 @@ def instantiate_generator_class(builder: IRBuilder) -> Value:
122128
fitem = builder.fn_info.fitem
123129
generator_reg = builder.add(Call(builder.fn_info.generator_class.ir.ctor, [], fitem.line))
124130

125-
# Get the current environment register. If the current function is nested, then the
126-
# generator class gets instantiated from the callable class' '__call__' method, and hence
127-
# we use the callable class' environment register. Otherwise, we use the original
128-
# function's environment register.
129-
if builder.fn_info.is_nested:
130-
curr_env_reg = builder.fn_info.callable_class.curr_env_reg
131+
if builder.fn_info.can_merge_generator_and_env_classes():
132+
# Set the generator instance to the initial state (zero).
133+
zero = Integer(0)
134+
builder.add(SetAttr(generator_reg, NEXT_LABEL_ATTR_NAME, zero, fitem.line))
131135
else:
132-
curr_env_reg = builder.fn_info.curr_env_reg
133-
134-
# Set the generator class' environment attribute to point at the environment class
135-
# defined in the current scope.
136-
builder.add(SetAttr(generator_reg, ENV_ATTR_NAME, curr_env_reg, fitem.line))
137-
138-
# Set the generator class' environment class' NEXT_LABEL_ATTR_NAME attribute to 0.
139-
zero = Integer(0)
140-
builder.add(SetAttr(curr_env_reg, NEXT_LABEL_ATTR_NAME, zero, fitem.line))
136+
# Get the current environment register. If the current function is nested, then the
137+
# generator class gets instantiated from the callable class' '__call__' method, and hence
138+
# we use the callable class' environment register. Otherwise, we use the original
139+
# function's environment register.
140+
if builder.fn_info.is_nested:
141+
curr_env_reg = builder.fn_info.callable_class.curr_env_reg
142+
else:
143+
curr_env_reg = builder.fn_info.curr_env_reg
144+
145+
# Set the generator class' environment attribute to point at the environment class
146+
# defined in the current scope.
147+
builder.add(SetAttr(generator_reg, ENV_ATTR_NAME, curr_env_reg, fitem.line))
148+
149+
# Set the generator instance's environment to the initial state (zero).
150+
zero = Integer(0)
151+
builder.add(SetAttr(curr_env_reg, NEXT_LABEL_ATTR_NAME, zero, fitem.line))
141152
return generator_reg
142153

143154

144155
def setup_generator_class(builder: IRBuilder) -> ClassIR:
145156
name = f"{builder.fn_info.namespaced_name()}_gen"
146157

147158
generator_class_ir = ClassIR(name, builder.module_name, is_generated=True)
148-
generator_class_ir.attributes[ENV_ATTR_NAME] = RInstance(builder.fn_info.env_class)
159+
if builder.fn_info.can_merge_generator_and_env_classes():
160+
builder.fn_info.env_class = generator_class_ir
161+
else:
162+
generator_class_ir.attributes[ENV_ATTR_NAME] = RInstance(builder.fn_info.env_class)
149163
generator_class_ir.mro = [generator_class_ir]
150164

151165
builder.classes.append(generator_class_ir)
@@ -392,7 +406,10 @@ def setup_env_for_generator_class(builder: IRBuilder) -> None:
392406
cls.send_arg_reg = exc_arg
393407

394408
cls.self_reg = builder.read(self_target, fitem.line)
395-
cls.curr_env_reg = load_outer_env(builder, cls.self_reg, builder.symtables[-1])
409+
if builder.fn_info.can_merge_generator_and_env_classes():
410+
cls.curr_env_reg = cls.self_reg
411+
else:
412+
cls.curr_env_reg = load_outer_env(builder, cls.self_reg, builder.symtables[-1])
396413

397414
# Define a variable representing the label to go to the next time
398415
# the '__next__' function of the generator is called, and add it

mypyc/namegen.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,20 +34,30 @@ class NameGenerator:
3434
3535
The generated should be internal to a build and thus the mapping is
3636
arbitrary. Just generating names '1', '2', ... would be correct,
37-
though not very usable.
37+
though not very usable. The generated names may be visible in CPU
38+
profiles and when debugging using native debuggers.
3839
"""
3940

40-
def __init__(self, groups: Iterable[list[str]]) -> None:
41+
def __init__(self, groups: Iterable[list[str]], *, separate: bool = False) -> None:
4142
"""Initialize with a list of modules in each compilation group.
4243
4344
The names of modules are used to shorten names referring to
4445
modules, for convenience. Arbitrary module
4546
names are supported for generated names, but uncompiled modules
4647
will use long names.
48+
49+
If separate is True, assume separate compilation. This implies
50+
that we don't have knowledge of all sources that will be linked
51+
together. In this case we won't trim module prefixes, since we
52+
don't have enough information to determine common module prefixes.
4753
"""
4854
self.module_map: dict[str, str] = {}
4955
for names in groups:
50-
self.module_map.update(make_module_translation_map(names))
56+
if not separate:
57+
self.module_map.update(make_module_translation_map(names))
58+
else:
59+
for name in names:
60+
self.module_map[name] = name + "."
5161
self.translations: dict[tuple[str, str], str] = {}
5262
self.used_names: set[str] = set()
5363

mypyc/test-data/run-async.test

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,3 +561,85 @@ def test_bool() -> None:
561561

562562
[file asyncio/__init__.pyi]
563563
def run(x: object) -> object: ...
564+
565+
[case testRunAsyncNestedFunctions]
566+
import asyncio
567+
from typing import cast, Iterator
568+
569+
from testutil import assertRaises
570+
571+
def normal_contains_async_def(x: int) -> int:
572+
async def f(y: int) -> int:
573+
return x + y
574+
575+
return 5 + cast(int, asyncio.run(f(6)))
576+
577+
def test_def_contains_async_def() -> None:
578+
assert normal_contains_async_def(3) == 14
579+
580+
async def inc(x: int) -> int:
581+
return x + 1
582+
583+
async def async_def_contains_normal(x: int) -> int:
584+
def nested(y: int, z: int) -> int:
585+
return x + y + z
586+
587+
a = x
588+
a += nested((await inc(3)), (await inc(4)))
589+
return a
590+
591+
def test_async_def_contains_normal() -> None:
592+
assert normal_contains_async_def(2) == (2 + 2 + 4 + 5)
593+
594+
async def async_def_contains_async_def(x: int) -> int:
595+
async def f(y: int) -> int:
596+
return (await inc(x)) + (await inc(y))
597+
598+
return (await f(1)) + (await f(2))
599+
600+
def test_async_def_contains_async_def() -> None:
601+
assert asyncio.run(async_def_contains_async_def(3)) == (3 + 1 + 1 + 1) + (3 + 1 + 2 + 1)
602+
603+
async def async_def_contains_generator(x: int) -> tuple[int, int, int]:
604+
def gen(y: int) -> Iterator[int]:
605+
yield x + 1
606+
yield x + y
607+
608+
it = gen(4)
609+
res = x + 10, next(it), next(it)
610+
611+
with assertRaises(StopIteration):
612+
next(it)
613+
614+
return res
615+
616+
def test_async_def_contains_generator() -> None:
617+
assert asyncio.run(async_def_contains_generator(3)) == (13, 4, 7)
618+
619+
def generator_contains_async_def(x: int) -> Iterator[int]:
620+
async def f(y: int) -> int:
621+
return (await inc(x)) + (await inc(y))
622+
623+
yield cast(int, asyncio.run(f(2)))
624+
yield cast(int, asyncio.run(f(3)))
625+
yield x + 10
626+
627+
def test_generator_contains_async_def() -> None:
628+
assert list(generator_contains_async_def(5)) == [6 + 3, 6 + 4, 15]
629+
630+
async def async_def_contains_two_nested_functions(x: int, y: int) -> tuple[int, int]:
631+
def f(a: int) -> int:
632+
return x + a
633+
634+
def g(b: int, c: int) -> int:
635+
return y + b + c
636+
637+
return (await inc(f(3))), (await inc(g(4, 10)))
638+
639+
def test_async_def_contains_two_nested_functions() -> None:
640+
assert asyncio.run(async_def_contains_two_nested_functions(5, 7)) == (
641+
(5 + 3 + 1), (7 + 4 + 10 + 1)
642+
)
643+
644+
[file asyncio/__init__.pyi]
645+
def run(x: object) -> object: ...

mypyc/test-data/run-classes.test

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2754,6 +2754,21 @@ def test_function():
27542754
assert(isinstance(d.fitem, ForwardDefinedClass))
27552755
assert(isinstance(d.fitems, ForwardDefinedClass))
27562756

2757+
[case testDelForDictSubclass-xfail]
2758+
# The crash in issue mypy#19175 is fixed.
2759+
# But, for classes that derive from built-in Python classes, user-defined __del__ method is not
2760+
# being invoked.
2761+
class DictSubclass(dict):
2762+
def __del__(self):
2763+
print("deleting DictSubclass...")
2764+
2765+
[file driver.py]
2766+
import native
2767+
native.DictSubclass()
2768+
2769+
[out]
2770+
deleting DictSubclass...
2771+
27572772
[case testDel]
27582773
class A:
27592774
def __del__(self):
@@ -2774,6 +2789,12 @@ class C(B):
27742789
class D(A):
27752790
pass
27762791

2792+
# Just make sure that this class compiles (see issue mypy#19175). testDelForDictSubclass tests for
2793+
# correct output.
2794+
class NormDict(dict):
2795+
def __del__(self) -> None:
2796+
pass
2797+
27772798
[file driver.py]
27782799
import native
27792800
native.C()

mypyc/test/test_namegen.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,17 @@ def test_name_generator(self) -> None:
5252
assert g.private_name("foo", "C_x_y") == "foo___C_x_y"
5353
assert g.private_name("foo", "C_x_y") == "foo___C_x_y"
5454
assert g.private_name("foo", "___") == "foo______3_"
55+
56+
g = NameGenerator([["foo.zar"]])
57+
assert g.private_name("foo.zar", "f") == "f"
58+
59+
def test_name_generator_with_separate(self) -> None:
60+
g = NameGenerator([["foo", "foo.zar"]], separate=True)
61+
assert g.private_name("foo", "f") == "foo___f"
62+
assert g.private_name("foo", "C.x.y") == "foo___C___x___y"
63+
assert g.private_name("foo.zar", "C.x.y") == "foo___zar___C___x___y"
64+
assert g.private_name("foo", "C.x_y") == "foo___C___x_y"
65+
assert g.private_name("foo", "___") == "foo______3_"
66+
67+
g = NameGenerator([["foo.zar"]], separate=True)
68+
assert g.private_name("foo.zar", "f") == "foo___zar___f"

0 commit comments

Comments
 (0)