Skip to content

Commit eb07c06

Browse files
authored
[mypyc] Report error when registering a nested function (#19450)
Fixes mypyc/mypyc#1118 Added a check for nested `@singledispatch` functions and nested functions registered to `@singledispatch` functions to report an error in mypyc. Currently either of those cases causes mypyc to crash because of the different handling of nested and top-level functions. Changed to abort the compilation early when these errors are found so that in the transform code we can assume that the singledispatch functions are valid. This means that mypyc might not report as many errors until it quits as before, so I have split the error output test in `commandline.test` into two.
1 parent b546953 commit eb07c06

File tree

4 files changed

+133
-39
lines changed

4 files changed

+133
-39
lines changed

mypyc/irbuild/main.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ def build_ir(
7171
singledispatch_info = find_singledispatch_register_impls(modules, errors)
7272

7373
result: ModuleIRs = {}
74+
if errors.num_errors > 0:
75+
return result
7476

7577
# Generate IR for all modules.
7678
class_irs = []

mypyc/irbuild/prepare.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,12 @@ def __init__(self, errors: Errors) -> None:
616616
self.decorators_to_remove: dict[FuncDef, list[int]] = {}
617617

618618
self.errors: Errors = errors
619+
self.func_stack_depth = 0
620+
621+
def visit_func_def(self, o: FuncDef) -> None:
622+
self.func_stack_depth += 1
623+
super().visit_func_def(o)
624+
self.func_stack_depth -= 1
619625

620626
def visit_decorator(self, dec: Decorator) -> None:
621627
if dec.decorators:
@@ -627,6 +633,10 @@ def visit_decorator(self, dec: Decorator) -> None:
627633
for i, d in enumerate(decorators_to_store):
628634
impl = get_singledispatch_register_call_info(d, dec.func)
629635
if impl is not None:
636+
if self.func_stack_depth > 0:
637+
self.errors.error(
638+
"Registering nested functions not supported", self.current_path, d.line
639+
)
630640
self.singledispatch_impls[impl.singledispatch_func].append(
631641
(impl.dispatch_type, dec.func)
632642
)
@@ -643,6 +653,12 @@ def visit_decorator(self, dec: Decorator) -> None:
643653
)
644654
else:
645655
if refers_to_fullname(d, "functools.singledispatch"):
656+
if self.func_stack_depth > 0:
657+
self.errors.error(
658+
"Nested singledispatch functions not supported",
659+
self.current_path,
660+
d.line,
661+
)
646662
decorators_to_remove.append(i)
647663
# make sure that we still treat the function as a singledispatch function
648664
# even if we don't find any registered implementations (which might happen

mypyc/test-data/commandline.test

Lines changed: 60 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,71 @@ assert a.f(10) == 100
101101
def f(x: int) -> int:
102102
return x*x
103103

104-
[case testErrorOutput]
104+
[case testErrorOutput1]
105+
# cmd: test.py
106+
107+
[file test.py]
108+
from functools import singledispatch
109+
from mypy_extensions import trait
110+
from typing import Any
111+
112+
def decorator(x: Any) -> Any:
113+
return x
114+
115+
class NeverMetaclass(type): # E: Inheriting from most builtin types is unimplemented \
116+
# N: Potential workaround: @mypy_extensions.mypyc_attr(native_class=False) \
117+
# N: https://mypyc.readthedocs.io/en/stable/native_classes.html#defining-non-native-classes
118+
pass
119+
120+
class Concrete1:
121+
pass
122+
123+
@trait
124+
class Trait1:
125+
pass
126+
127+
class Concrete2:
128+
pass
129+
130+
@decorator
131+
class NonExt(Concrete1): # E: Non-extension classes may not inherit from extension classes
132+
pass
133+
134+
class NopeMultipleInheritanceAndBadOrder3(Trait1, Concrete1, Concrete2): # E: Non-trait base must appear first in parent list
135+
pass
136+
137+
class NopeBadOrder(Trait1, Concrete2): # E: Non-trait base must appear first in parent list
138+
pass
139+
140+
class Foo:
141+
pass
142+
143+
@singledispatch
144+
def a(arg) -> None:
145+
pass
146+
147+
@decorator # E: Calling decorator after registering function not supported
148+
@a.register
149+
def g(arg: int) -> None:
150+
pass
151+
152+
@a.register
153+
@decorator
154+
def h(arg: str) -> None:
155+
pass
156+
157+
@decorator
158+
@decorator # E: Calling decorator after registering function not supported
159+
@a.register
160+
def i(arg: Foo) -> None:
161+
pass
162+
163+
[case testErrorOutput2]
105164
# cmd: test.py
106165

107166
[file test.py]
108167
from typing import Final, List, Any, AsyncIterable
109168
from mypy_extensions import trait, mypyc_attr
110-
from functools import singledispatch
111169

112170
def busted(b: bool) -> None:
113171
for i in range(1, 10, 0): # E: range() step can't be zero
@@ -138,11 +196,6 @@ Foo.lol = 50 # E: Only class variables defined as ClassVar can be assigned to
138196
def decorator(x: Any) -> Any:
139197
return x
140198

141-
class NeverMetaclass(type): # E: Inheriting from most builtin types is unimplemented \
142-
# N: Potential workaround: @mypy_extensions.mypyc_attr(native_class=False) \
143-
# N: https://mypyc.readthedocs.io/en/stable/native_classes.html#defining-non-native-classes
144-
pass
145-
146199
class Concrete1:
147200
pass
148201

@@ -161,11 +214,6 @@ class Concrete2:
161214
class Trait2(Concrete2):
162215
pass
163216

164-
@decorator
165-
class NonExt(Concrete1): # E: Non-extension classes may not inherit from extension classes
166-
pass
167-
168-
169217
class NopeMultipleInheritance(Concrete1, Concrete2): # E: Multiple inheritance is not supported (except for traits)
170218
pass
171219

@@ -175,13 +223,6 @@ class NopeMultipleInheritanceAndBadOrder(Concrete1, Trait1, Concrete2): # E: Mu
175223
class NopeMultipleInheritanceAndBadOrder2(Concrete1, Concrete2, Trait1): # E: Multiple inheritance is not supported (except for traits)
176224
pass
177225

178-
class NopeMultipleInheritanceAndBadOrder3(Trait1, Concrete1, Concrete2): # E: Non-trait base must appear first in parent list # E: Multiple inheritance is not supported (except for traits)
179-
pass
180-
181-
class NopeBadOrder(Trait1, Concrete2): # E: Non-trait base must appear first in parent list
182-
pass
183-
184-
185226
@decorator
186227
class NonExt2:
187228
@property # E: Property setters not supported in non-extension classes
@@ -219,26 +260,6 @@ class AllowInterp2(PureTrait): # E: Base class "test.PureTrait" does not allow
219260
async def async_generators() -> AsyncIterable[int]:
220261
yield 1 # E: async generators are unimplemented
221262

222-
@singledispatch
223-
def a(arg) -> None:
224-
pass
225-
226-
@decorator # E: Calling decorator after registering function not supported
227-
@a.register
228-
def g(arg: int) -> None:
229-
pass
230-
231-
@a.register
232-
@decorator
233-
def h(arg: str) -> None:
234-
pass
235-
236-
@decorator
237-
@decorator # E: Calling decorator after registering function not supported
238-
@a.register
239-
def i(arg: Foo) -> None:
240-
pass
241-
242263
[case testOnlyWarningOutput]
243264
# cmd: test.py
244265

mypyc/test-data/irbuild-singledispatch.test

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,3 +274,58 @@ L0:
274274
r1 = f(r0)
275275
r2 = box(None, 1)
276276
return r2
277+
278+
[case registerNestedFunctionError]
279+
from functools import singledispatch
280+
from typing import Any, overload
281+
282+
def dec(x: Any) -> Any:
283+
return x
284+
285+
def f() -> None:
286+
@singledispatch # E: Nested singledispatch functions not supported
287+
def singledispatch_in_func(x: Any) -> None:
288+
pass
289+
290+
@dec
291+
def g() -> None:
292+
@singledispatch # E: Nested singledispatch functions not supported
293+
def singledispatch_in_decorated(x: Any) -> None:
294+
pass
295+
296+
@overload
297+
def h(x: int) -> None:
298+
pass
299+
@overload
300+
def h(x: str) -> None:
301+
pass
302+
def h(x: Any) -> None:
303+
@singledispatch # E: Nested singledispatch functions not supported
304+
def singledispatch_in_overload(x: Any) -> None:
305+
pass
306+
307+
@singledispatch
308+
def outside(x: Any) -> None:
309+
pass
310+
311+
def i() -> None:
312+
@outside.register # E: Registering nested functions not supported
313+
def register_in_func(x: int) -> None:
314+
pass
315+
316+
@dec
317+
def j() -> None:
318+
@outside.register # E: Registering nested functions not supported
319+
def register_in_decorated(x: int) -> None:
320+
pass
321+
322+
@overload
323+
def k(x: int) -> None:
324+
pass
325+
@overload
326+
def k(x: str) -> None:
327+
pass
328+
def k(x: Any) -> None:
329+
@outside.register # E: Registering nested functions not supported
330+
def register_in_overload(x: int) -> None:
331+
pass

0 commit comments

Comments
 (0)