summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorda-woods <dw-git@d-woods.co.uk>2020-03-03 13:01:18 +0000
committerGitHub <noreply@github.com>2020-03-03 14:01:18 +0100
commita8cb127df0cb970b00a8f58797740ef571dc8817 (patch)
tree3e91b2ec4a5cd382552b4617f4ec779c185f2ec8
parentf6bf6aa9c7d2414b54e7289639ae5f43b15ede05 (diff)
downloadcython-a8cb127df0cb970b00a8f58797740ef571dc8817.tar.gz
Support fused arguments specified by annotation or locals (GH-3391)
1. DefNode.has_fused_arguments was set too early (before locals/annotations) were evalutated, so function was not treated as fused. 2. When re-evaluating the specializations of the fused function it was treated as a redefinition because the locals/annotation was reapplied over the specialized type. 3. Including annotation as string (required changes to StringNode.analyse_as_type), and extra tests for fused type defined as cython.fused_type in the Py file
-rw-r--r--Cython/Compiler/ExprNodes.py6
-rw-r--r--Cython/Compiler/Nodes.py13
-rw-r--r--tests/run/pure_fused.pxd6
-rw-r--r--tests/run/pure_fused.py60
4 files changed, 76 insertions, 9 deletions
diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py
index 48e6feadc..1915369df 100644
--- a/Cython/Compiler/ExprNodes.py
+++ b/Cython/Compiler/ExprNodes.py
@@ -1434,11 +1434,7 @@ def _analyse_name_as_type(name, pos, env):
return type
global_entry = env.global_scope().lookup(name)
- if global_entry and global_entry.type and (
- global_entry.type.is_extension_type
- or global_entry.type.is_struct_or_union
- or global_entry.type.is_builtin_type
- or global_entry.type.is_cpp_class):
+ if global_entry and global_entry.is_type and global_entry.type:
return global_entry.type
from .TreeFragment import TreeFragment
diff --git a/Cython/Compiler/Nodes.py b/Cython/Compiler/Nodes.py
index 9a63bc550..9b5dc2591 100644
--- a/Cython/Compiler/Nodes.py
+++ b/Cython/Compiler/Nodes.py
@@ -877,7 +877,10 @@ class CArgDeclNode(Node):
# inject type declaration from annotations
# this is called without 'env' by AdjustDefByDirectives transform before declaration analysis
- if self.annotation and env and env.directives['annotation_typing'] and self.base_type.name is None:
+ if (self.annotation and env and env.directives['annotation_typing']
+ # CSimpleBaseTypeNode has a name attribute; CAnalysedBaseTypeNode
+ # (and maybe other options) doesn't
+ and getattr(self.base_type, "name", None) is None):
arg_type = self.inject_type_from_annotations(env)
if arg_type is not None:
base_type = arg_type
@@ -1678,6 +1681,8 @@ class FuncDefNode(StatNode, BlockNode):
return arg
if other_type is None:
error(type_node.pos, "Not a type")
+ elif other_type.is_fused and any(orig_type.same_as(t) for t in other_type.types):
+ pass # use specialized rather than fused type
elif orig_type is not py_object_type and not orig_type.same_as(other_type):
error(arg.base_type.pos, "Signature does not agree with previous declaration")
error(type_node.pos, "Previous declaration here")
@@ -2934,9 +2939,6 @@ class DefNode(FuncDefNode):
arg.name = name_declarator.name
arg.type = type
- if type.is_fused:
- self.has_fused_arguments = True
-
self.align_argument_type(env, arg)
if name_declarator and name_declarator.cname:
error(self.pos, "Python function argument cannot have C name specification")
@@ -2967,6 +2969,9 @@ class DefNode(FuncDefNode):
error(arg.pos, "Only Python type arguments can have 'not None'")
if arg.or_none:
error(arg.pos, "Only Python type arguments can have 'or None'")
+
+ if arg.type.is_fused:
+ self.has_fused_arguments = True
env.fused_to_specific = f2s
if has_np_pythran(env):
diff --git a/tests/run/pure_fused.pxd b/tests/run/pure_fused.pxd
new file mode 100644
index 000000000..8517227d0
--- /dev/null
+++ b/tests/run/pure_fused.pxd
@@ -0,0 +1,6 @@
+ctypedef fused NotInPy:
+ int
+ float
+
+cdef class TestCls:
+ cpdef cpfunc(self, NotInPy arg)
diff --git a/tests/run/pure_fused.py b/tests/run/pure_fused.py
new file mode 100644
index 000000000..b6d211e0f
--- /dev/null
+++ b/tests/run/pure_fused.py
@@ -0,0 +1,60 @@
+# mode: run
+# tag: fused, pure3.0
+
+#cython: annotation_typing=True
+
+import cython
+
+InPy = cython.fused_type(cython.int, cython.float)
+
+class TestCls:
+ # although annotations as strings isn't recommended and generates a warning
+ # it does allow the test to run on more (pure) Python versions
+ def func1(self, arg: 'NotInPy'):
+ """
+ >>> TestCls().func1(1.0)
+ 'float'
+ >>> TestCls().func1(2)
+ 'int'
+ """
+ return cython.typeof(arg)
+
+ if cython.compiled:
+ @cython.locals(arg = NotInPy) # NameError in pure Python
+ def func2(self, arg):
+ """
+ >>> TestCls().func2(1.0)
+ 'float'
+ >>> TestCls().func2(2)
+ 'int'
+ """
+ return cython.typeof(arg)
+
+ def cpfunc(self, arg):
+ """
+ >>> TestCls().cpfunc(1.0)
+ 'float'
+ >>> TestCls().cpfunc(2)
+ 'int'
+ """
+ return cython.typeof(arg)
+
+ def func1_inpy(self, arg: InPy):
+ """
+ >>> TestCls().func1_inpy(1.0)
+ 'float'
+ >>> TestCls().func1_inpy(2)
+ 'int'
+ """
+ return cython.typeof(arg)
+
+ @cython.locals(arg = InPy)
+ def func2_inpy(self, arg):
+ """
+ >>> TestCls().func2_inpy(1.0)
+ 'float'
+ >>> TestCls().func2_inpy(2)
+ 'int'
+ """
+ return cython.typeof(arg)
+