Commit 192925d8 authored by da-woods's avatar da-woods Committed by GitHub

Prevent fused dispatcher optional arguments being overwritten (GH-3519)

* Prevent fused dispatcher optional arguments being overwritten

If the 5th argument of the regular functions was optional it'd
overwrite the _fused_sigindex default of the dispatcher, causing
type errors at runtime.

closes https://github.com/cython/cython/issues/3511
parent c1f3fd28
...@@ -9449,15 +9449,19 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin): ...@@ -9449,15 +9449,19 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin):
if self.defaults_tuple: if self.defaults_tuple:
code.putln('__Pyx_CyFunction_SetDefaultsTuple(%s, %s);' % ( code.putln('__Pyx_CyFunction_SetDefaultsTuple(%s, %s);' % (
self.result(), self.defaults_tuple.py_result())) self.result(), self.defaults_tuple.py_result()))
if self.defaults_kwdict: if not self.specialized_cpdefs:
code.putln('__Pyx_CyFunction_SetDefaultsKwDict(%s, %s);' % ( # disable introspection functions for fused dispatcher function since the user never sees it
self.result(), self.defaults_kwdict.py_result())) # TODO: this is mostly disabled because the attributes end up pointing to ones belonging
if def_node.defaults_getter: # to the specializations - ideally this would be fixed instead
code.putln('__Pyx_CyFunction_SetDefaultsGetter(%s, %s);' % ( if self.defaults_kwdict:
self.result(), def_node.defaults_getter.entry.pyfunc_cname)) code.putln('__Pyx_CyFunction_SetDefaultsKwDict(%s, %s);' % (
if self.annotations_dict: self.result(), self.defaults_kwdict.py_result()))
code.putln('__Pyx_CyFunction_SetAnnotationsDict(%s, %s);' % ( if def_node.defaults_getter:
self.result(), self.annotations_dict.py_result())) code.putln('__Pyx_CyFunction_SetDefaultsGetter(%s, %s);' % (
self.result(), def_node.defaults_getter.entry.pyfunc_cname))
if self.annotations_dict:
code.putln('__Pyx_CyFunction_SetAnnotationsDict(%s, %s);' % (
self.result(), self.annotations_dict.py_result()))
class InnerFunctionNode(PyCFunctionNode): class InnerFunctionNode(PyCFunctionNode):
......
...@@ -839,7 +839,8 @@ class FusedCFuncDefNode(StatListNode): ...@@ -839,7 +839,8 @@ class FusedCFuncDefNode(StatListNode):
for i, stat in enumerate(self.stats): for i, stat in enumerate(self.stats):
stat = self.stats[i] = stat.analyse_expressions(env) stat = self.stats[i] = stat.analyse_expressions(env)
if isinstance(stat, FuncDefNode): if isinstance(stat, FuncDefNode) and stat is not self.py_func:
# the dispatcher specifically doesn't want its defaults overriding
for arg, default in zip(stat.args, defaults): for arg, default in zip(stat.args, defaults):
if default is not None: if default is not None:
arg.default = CloneNode(default).coerce_to(arg.type, env) arg.default = CloneNode(default).coerce_to(arg.type, env)
......
...@@ -54,9 +54,13 @@ f = 5.6 ...@@ -54,9 +54,13 @@ f = 5.6
i = 9 i = 9
def opt_func(fused_t obj, cython.floating myf = 1.2, cython.integral myi = 7): def opt_func(fused_t obj, cython.floating myf = 1.2, cython.integral myi = 7,
another_opt = 2, yet_another_opt=3):
""" """
Test runtime dispatch, indexing of various kinds and optional arguments Test runtime dispatch, indexing of various kinds and optional arguments.
Use 5 arguments because at one point the optional argument from the
5th argument was overwriting that of the __pyx_fused dispatcher.
https://github.com/cython/cython/issues/3511
>>> opt_func("spam", f, i) >>> opt_func("spam", f, i)
str object double long str object double long
...@@ -121,9 +125,9 @@ def opt_func(fused_t obj, cython.floating myf = 1.2, cython.integral myi = 7): ...@@ -121,9 +125,9 @@ def opt_func(fused_t obj, cython.floating myf = 1.2, cython.integral myi = 7):
>>> opt_func() >>> opt_func()
Traceback (most recent call last): Traceback (most recent call last):
TypeError: Expected at least 1 argument, got 0 TypeError: Expected at least 1 argument, got 0
>>> opt_func("abc", f, i, 5) # doctest: +ELLIPSIS >>> opt_func("abc", f, i, 5, 5, 5) # doctest: +ELLIPSIS
Traceback (most recent call last): Traceback (most recent call last):
TypeError: ...at most 3... TypeError: ...at most 5...
>>> opt_func[ExtClassA, cy.float, cy.long](object(), f) >>> opt_func[ExtClassA, cy.float, cy.long](object(), f)
Traceback (most recent call last): Traceback (most recent call last):
TypeError: Argument 'obj' has incorrect type (expected fused_def.ExtClassA, got object) TypeError: Argument 'obj' has incorrect type (expected fused_def.ExtClassA, got object)
...@@ -154,19 +158,19 @@ def test_opt_func(): ...@@ -154,19 +158,19 @@ def test_opt_func():
def test_opt_func_introspection(): def test_opt_func_introspection():
""" """
>>> opt_func.__defaults__ >>> opt_func.__defaults__
(1.2, 7) (1.2, 7, 2, 3)
>>> opt_func.__kwdefaults__ >>> opt_func.__kwdefaults__
>>> opt_func.__annotations__ >>> opt_func.__annotations__
{} {}
>>> opt_func[str, float, int].__defaults__ >>> opt_func[str, float, int].__defaults__
(1.2, 7) (1.2, 7, 2, 3)
>>> opt_func[str, float, int].__kwdefaults__ >>> opt_func[str, float, int].__kwdefaults__
>>> opt_func[str, float, int].__annotations__ >>> opt_func[str, float, int].__annotations__
{} {}
>>> opt_func[str, cy.double, cy.long].__defaults__ >>> opt_func[str, cy.double, cy.long].__defaults__
(1.2, 7) (1.2, 7, 2, 3)
>>> opt_func[str, cy.double, cy.long].__kwdefaults__ >>> opt_func[str, cy.double, cy.long].__kwdefaults__
>>> opt_func[str, cy.double, cy.long].__annotations__ >>> opt_func[str, cy.double, cy.long].__annotations__
{} {}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment