Skip to content

Commit d8a43fb

Browse files
authored
[mypyc] Fix C name conflict with conditional nested async funcs (#22330)
Fixes mypyc/mypyc#1182. Multiple conditional definitions of a nested async function or generator with the same name generated invalid C (redefinition errors): ```python async def f(flag: bool) -> str: if flag: async def inner() -> str: return "a" else: async def inner() -> str: return "b" return await inner() ``` The environment classes of the nested functions had the same name. Now we make the names unique, similar to the names of callable classes.
1 parent 8c5f571 commit d8a43fb

5 files changed

Lines changed: 75 additions & 23 deletions

File tree

‎mypyc/irbuild/builder.py‎

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,9 @@ def __init__(
250250
self.classes: list[ClassIR] = []
251251
self.final_names: list[tuple[str, RType]] = []
252252
self.type_var_names: list[str] = []
253-
self.callable_class_names: set[str] = set()
253+
# Names of generated classes (callable and environment classes), used to
254+
# keep the names unique within the module
255+
self.generated_class_names: set[str] = set()
254256
self.options = options
255257

256258
# Keep track of the number of lambdas instantiated so we avoid name conflicts.
@@ -318,6 +320,27 @@ def __init__(
318320

319321
# High-level control
320322

323+
def unique_class_name(self, base_name: str) -> str:
324+
"""Return a unique name for a generated class, based on base_name.
325+
326+
We allow multiple definitions of a nested function with the same name,
327+
since this is valid in if-else blocks. Example:
328+
329+
if True:
330+
def foo(): ----> foo_obj()
331+
return True
332+
else:
333+
def foo(): ----> foo_obj_0()
334+
return False
335+
"""
336+
name = base_name
337+
count = 0
338+
while name in self.generated_class_names:
339+
name = f"{base_name}_{count}"
340+
count += 1
341+
self.generated_class_names.add(name)
342+
return name
343+
321344
def set_module(self, module_name: str, module_path: str) -> None:
322345
"""Set the name and path of the current module.
323346

‎mypyc/irbuild/callable_class.py‎

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -54,22 +54,7 @@ def setup_callable_class(builder: IRBuilder) -> None:
5454
Return a newly constructed ClassIR representing the callable
5555
class for the nested function.
5656
"""
57-
# Check to see that the name has not already been taken. If so,
58-
# rename the class. We allow multiple uses of the same function
59-
# name because this is valid in if-else blocks. Example:
60-
#
61-
# if True:
62-
# def foo(): ----> foo_obj()
63-
# return True
64-
# else:
65-
# def foo(): ----> foo_obj_0()
66-
# return False
67-
name = base_name = f"{builder.fn_info.namespaced_name()}_obj"
68-
count = 0
69-
while name in builder.callable_class_names:
70-
name = base_name + "_" + str(count)
71-
count += 1
72-
builder.callable_class_names.add(name)
57+
name = builder.unique_class_name(f"{builder.fn_info.namespaced_name()}_obj")
7358

7459
# Define the actual callable class ClassIR, and set its
7560
# environment to point at the previously defined environment

‎mypyc/irbuild/env_class.py‎

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,8 @@ class is generated, the function environment has not yet been
4949
Return a ClassIR representing an environment for a function
5050
containing a nested function.
5151
"""
52-
env_class = ClassIR(
53-
f"{builder.fn_info.namespaced_name()}_env",
54-
builder.module_name,
55-
is_generated=True,
56-
is_final_class=True,
57-
)
52+
name = builder.unique_class_name(f"{builder.fn_info.namespaced_name()}_env")
53+
env_class = ClassIR(name, builder.module_name, is_generated=True, is_final_class=True)
5854
env_class.reuse_freed_instance = True
5955
env_class.attributes[SELF_NAME] = RInstance(env_class)
6056
if builder.fn_info.is_nested and builder.fn_infos[-2]._env_class is not None:

‎mypyc/test-data/run-async.test‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -943,6 +943,36 @@ async def async_def_contains_decorated_async_def(n: int) -> int:
943943
async def test_async_def_contains_decorated_async_def() -> None:
944944
assert await async_def_contains_decorated_async_def(7) == 10
945945

946+
async def conditional_nested_async(flag: int) -> str:
947+
if flag < 0:
948+
async def inner() -> str:
949+
return "first"
950+
elif flag > 0:
951+
async def inner() -> str:
952+
return "second"
953+
else:
954+
async def inner() -> str:
955+
return "third"
956+
return await inner()
957+
958+
async def test_conditional_nested_async_defs_with_same_name() -> None:
959+
assert await conditional_nested_async(-1) == "first"
960+
assert await conditional_nested_async(1) == "second"
961+
assert await conditional_nested_async(0) == "third"
962+
963+
def conditional_nested_generator(flag: bool) -> list[int]:
964+
if flag:
965+
def gen() -> Iterator[int]:
966+
yield 1
967+
else:
968+
def gen() -> Iterator[int]:
969+
yield 2
970+
return list(gen())
971+
972+
def test_conditional_nested_generators_with_same_name() -> None:
973+
assert conditional_nested_generator(True) == [1]
974+
assert conditional_nested_generator(False) == [2]
975+
946976
[file asyncio/__init__.pyi]
947977
def run(x: object) -> object: ...
948978

‎mypyc/test-data/run-functions.test‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1426,6 +1426,24 @@ def test_reassigned_nested_function() -> None:
14261426
assert outer(False) == [-1]
14271427
assert outer(True) == [0, 101]
14281428

1429+
def conditional_outer(flag: bool) -> int:
1430+
x = 1
1431+
if flag:
1432+
def inner() -> int:
1433+
def g() -> int:
1434+
return x + 10
1435+
return g()
1436+
else:
1437+
def inner() -> int:
1438+
def g() -> int:
1439+
return x + 20
1440+
return g()
1441+
return inner()
1442+
1443+
def test_conditional_nested_functions_with_nested_functions() -> None:
1444+
assert conditional_outer(True) == 11
1445+
assert conditional_outer(False) == 21
1446+
14291447
[case testStarArgFastPaths]
14301448
from typing import Any, Mapping
14311449
def fn(x: str, y: int) -> str:

0 commit comments

Comments
 (0)