From 61f7f5fd53e1dc207b3d32393d49bd2d3e611800 Mon Sep 17 00:00:00 2001 From: Emily Bourne Date: Tue, 23 Apr 2024 20:47:35 +0200 Subject: [PATCH 01/28] Raise correct error --- pyccel/codegen/wrapper/c_to_python_wrapper.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyccel/codegen/wrapper/c_to_python_wrapper.py b/pyccel/codegen/wrapper/c_to_python_wrapper.py index 460420f686..dec87ef206 100644 --- a/pyccel/codegen/wrapper/c_to_python_wrapper.py +++ b/pyccel/codegen/wrapper/c_to_python_wrapper.py @@ -282,7 +282,7 @@ def _get_check_function(self, py_obj, arg, raise_error): results = [FunctionDefResult(Variable(dtype, name = 'v'))]) func_call = FunctionCall(func, [py_obj]) - else: + elif isinstance(arg.class_type, NumpyNDArrayType): try : type_ref = numpy_dtype_registry[dtype] except KeyError: @@ -301,6 +301,9 @@ def _get_check_function(self, py_obj, arg, raise_error): # No error code required as the error is raised inside pyarray_check func_call = FunctionCall(check_func, [py_obj, type_ref, LiteralInteger(rank), flag]) + else: + errors.report(f"Can't check the type of an array of {arg.class_type}\n"+PYCCEL_RESTRICTION_TODO, + symbol=arg, severity='fatal') if raise_error: message = LiteralString(f"Expected an argument of type {arg.class_type} for argument {arg.name}") From e1994d728eee439df6a15c65d9e9641ba88b0863 Mon Sep 17 00:00:00 2001 From: Emily Bourne Date: Tue, 23 Apr 2024 21:58:55 +0200 Subject: [PATCH 02/28] Add C wrapping for homogeneous tuples as arguments --- pyccel/ast/cwrapper.py | 54 +++++++-- pyccel/codegen/wrapper/c_to_python_wrapper.py | 108 ++++++++++++------ 2 files changed, 120 insertions(+), 42 deletions(-) diff --git a/pyccel/ast/cwrapper.py b/pyccel/ast/cwrapper.py index 78592a11e2..78b41023d7 100644 --- a/pyccel/ast/cwrapper.py +++ b/pyccel/ast/cwrapper.py @@ -970,21 +970,12 @@ def declarations(self): arguments = [FunctionDefArgument(Variable(StringType(), name='_'))], results = [FunctionDefResult(Variable(PyccelPyObject(), name='o', memory_handling='alias'))]) -# https://docs.python.org/3/c-api/list.html#c.PyList_GetItem PyList_GetItem = FunctionDef(name = 'PyList_GetItem', body = [], arguments = [FunctionDefArgument(Variable(PyccelPyObject(), name='l', memory_handling='alias')), FunctionDefArgument(Variable(CNativeInt(), name='i'))], results = [FunctionDefResult(Variable(PyccelPyObject(), name='o', memory_handling='alias'))]) -# https://docs.python.org/3/c-api/list.html#c.PyList_SetItem -PyList_SetItem = FunctionDef(name = 'PyList_SetItem', - body = [], - arguments = [FunctionDefArgument(Variable(PyccelPyObject(), name='l', memory_handling='alias')), - FunctionDefArgument(Variable(CNativeInt(), name='i')), - FunctionDefArgument(Variable(PyccelPyObject(), name='new_item', memory_handling='alias'))], - results = []) - #------------------------------------------------------------------- #using the documentation of PyArg_ParseTuple() and Py_BuildValue https://docs.python.org/3/c-api/arg.html @@ -1083,28 +1074,73 @@ def C_to_Python(c_object): results = [FunctionDefResult(Variable(PythonNativeBool(), 'r'))], body = []) +#------------------------------------------------------------------- +# List functions +#------------------------------------------------------------------- + +# https://docs.python.org/3/c-api/list.html#c.PyList_New PyList_New = FunctionDef(name = 'PyList_New', arguments = [FunctionDefArgument(Variable(PythonNativeInt(), 'size'), value = LiteralInteger(0))], results = [FunctionDefResult(Variable(PyccelPyObject(), 'r', memory_handling='alias'))], body = []) +# https://docs.python.org/3/c-api/list.html#c.PyList_Append PyList_Append = FunctionDef(name = 'PyList_Append', arguments = [FunctionDefArgument(Variable(PyccelPyObject(), 'list', memory_handling='alias')), FunctionDefArgument(Variable(PyccelPyObject(), 'item', memory_handling='alias'))], results = [FunctionDefResult(Variable(CNativeInt(), 'i'))], body = []) +# https://docs.python.org/3/c-api/list.html#c.PyList_GetItem PyList_GetItem = FunctionDef(name = 'PyList_GetItem', arguments = [FunctionDefArgument(Variable(PyccelPyObject(), 'list', memory_handling='alias')), FunctionDefArgument(Variable(PythonNativeInt(), 'i'))], results = [FunctionDefResult(Variable(PyccelPyObject(), 'item', memory_handling='alias'))], body = []) +# https://docs.python.org/3/c-api/list.html#c.PyList_Size PyList_Size = FunctionDef(name = 'PyList_Size', arguments = [FunctionDefArgument(Variable(PyccelPyObject(), 'list', memory_handling='alias'))], results = [FunctionDefResult(Variable(PythonNativeInt(), 'i'))], body = []) +# https://docs.python.org/3/c-api/list.html#c.PyList_SetItem +PyList_SetItem = FunctionDef(name = 'PyList_SetItem', + body = [], + arguments = [FunctionDefArgument(Variable(PyccelPyObject(), name='l', memory_handling='alias')), + FunctionDefArgument(Variable(CNativeInt(), name='i')), + FunctionDefArgument(Variable(PyccelPyObject(), name='new_item', memory_handling='alias'))], + results = []) + +#------------------------------------------------------------------- +# Tuple functions +#------------------------------------------------------------------- + +# https://docs.python.org/3/c-api/tuple.html#c.PyTuple_New +PyTuple_New = FunctionDef(name = 'PyTuple_New', + arguments = [FunctionDefArgument(Variable(PythonNativeInt(), 'size'), value = LiteralInteger(0))], + results = [FunctionDefResult(Variable(PyccelPyObject(), 'tuple', memory_handling='alias'))], + body = []) + +# https://docs.python.org/3/c-api/tuple.html#c.PyTuple_Check +PyTuple_Check = FunctionDef(name = 'PyTuple_Check', + arguments = [FunctionDefArgument(Variable(PyccelPyObject(), 'tuple', memory_handling='alias'))], + results = [FunctionDefResult(Variable(PythonNativeInt(), 'i'))], + body = []) + +# https://docs.python.org/3/c-api/tuple.html#c.PyTuple_Size +PyTuple_Size = FunctionDef(name = 'PyTuple_Size', + arguments = [FunctionDefArgument(Variable(PyccelPyObject(), 'tuple', memory_handling='alias'))], + results = [FunctionDefResult(Variable(PythonNativeInt(), 'i'))], + body = []) + +# https://docs.python.org/3/c-api/tuple.html#c.PyTuple_GetItem +PyTuple_GetItem = FunctionDef(name = 'PyTuple_GetItem', + body = [], + arguments = [FunctionDefArgument(Variable(PyccelPyObject(), name='tuple', memory_handling='alias')), + FunctionDefArgument(Variable(CNativeInt(), name='i'))], + results = [FunctionDefResult(Variable(PyccelPyObject(), name='o', memory_handling='alias'))]) + # Functions definitions are defined in pyccel/stdlib/cwrapper/cwrapper.c check_type_registry = { diff --git a/pyccel/codegen/wrapper/c_to_python_wrapper.py b/pyccel/codegen/wrapper/c_to_python_wrapper.py index dec87ef206..8171446a58 100644 --- a/pyccel/codegen/wrapper/c_to_python_wrapper.py +++ b/pyccel/codegen/wrapper/c_to_python_wrapper.py @@ -11,13 +11,13 @@ from pyccel.ast.bind_c import BindCFunctionDef, BindCPointer, BindCFunctionDefArgument from pyccel.ast.bind_c import BindCModule, BindCVariable, BindCFunctionDefResult from pyccel.ast.bind_c import BindCClassDef, BindCClassProperty -from pyccel.ast.builtins import PythonTuple +from pyccel.ast.builtins import PythonTuple, PythonRange from pyccel.ast.class_defs import StackArrayClass from pyccel.ast.core import Interface, If, IfSection, Return, FunctionCall from pyccel.ast.core import FunctionDef, FunctionDefArgument, FunctionDefResult from pyccel.ast.core import Assign, AliasAssign, Deallocate, Allocate -from pyccel.ast.core import Import, Module, AugAssign, CommentBlock -from pyccel.ast.core import FunctionAddress, Declare, ClassDef, AsName +from pyccel.ast.core import Import, Module, AugAssign, CommentBlock, For +from pyccel.ast.core import FunctionAddress, Declare, ClassDef, AsName, Iterable from pyccel.ast.cwrapper import PyModule, PyccelPyObject, PyArgKeywords, PyModule_Create from pyccel.ast.cwrapper import PyArg_ParseTupleNode, Py_None, PyClassDef, PyModInitFunc from pyccel.ast.cwrapper import py_to_c_registry, check_type_registry, PyBuildValueNode @@ -29,9 +29,10 @@ from pyccel.ast.cwrapper import PyList_New, PyList_Append, PyList_GetItem, PyList_SetItem from pyccel.ast.cwrapper import PyccelPyTypeObject, PyCapsule_New, PyCapsule_Import from pyccel.ast.cwrapper import PySys_GetObject, PyUnicode_FromString, PyGetSetDefElement +from pyccel.ast.cwrapper import PyTuple_Size, PyTuple_Check, PyTuple_New, PyTuple_GetItem from pyccel.ast.c_concepts import ObjectAddress, PointerCast, CStackArray, CNativeInt from pyccel.ast.datatypes import VoidType, PythonNativeInt, CustomDataType, DataTypeFactory -from pyccel.ast.datatypes import FixedSizeNumericType +from pyccel.ast.datatypes import FixedSizeNumericType, HomogeneousTupleType from pyccel.ast.literals import Nil, LiteralTrue, LiteralString, LiteralInteger from pyccel.ast.literals import LiteralFalse from pyccel.ast.numpyext import NumpyNDArrayType @@ -301,6 +302,8 @@ def _get_check_function(self, py_obj, arg, raise_error): # No error code required as the error is raised inside pyarray_check func_call = FunctionCall(check_func, [py_obj, type_ref, LiteralInteger(rank), flag]) + elif isinstance(arg.class_type, HomogeneousTupleType): + func_call = FunctionCall(PyTuple_Check, [py_obj]) else: errors.report(f"Can't check the type of an array of {arg.class_type}\n"+PYCCEL_RESTRICTION_TODO, symbol=arg, severity='fatal') @@ -1447,35 +1450,7 @@ def _wrap_FunctionDefArgument(self, expr): # Collect the function which casts from a Python object to a C object dtype = orig_var.dtype - if isinstance(dtype, CustomDataType): - python_cls_base = self.scope.find(dtype.name, 'classes', raise_if_missing = True) - scope = python_cls_base.scope - attribute = scope.find('instance', 'variables', raise_if_missing = True) - if bound_argument: - cast_type = collect_arg - cast = [] - else: - cast_type = Variable(self._python_object_map[dtype], - self.scope.get_new_name(collect_arg.name), - memory_handling='alias', - cls_base = self.scope.find(dtype.name, 'classes', raise_if_missing = True)) - self.scope.insert_variable(cast_type) - cast = [AliasAssign(cast_type, PointerCast(collect_arg, cast_type))] - c_res = attribute.clone(attribute.name, new_class = DottedVariable, lhs = cast_type) - cast_c_res = PointerCast(c_res, orig_var) - cast.append(AliasAssign(arg_var, cast_c_res)) - elif arg_var.rank == 0: - try : - cast_function = py_to_c_registry[(dtype.primitive_type, dtype.precision)] - except KeyError: - errors.report(PYCCEL_RESTRICTION_TODO, symbol=dtype,severity='fatal') - cast_func = FunctionDef(name = cast_function, - body = [], - arguments = [FunctionDefArgument(Variable(PyccelPyObject(), name = 'o', memory_handling='alias'))], - results = [FunctionDefResult(Variable(dtype, name = 'v'))]) - cast = [Assign(arg_var, FunctionCall(cast_func, [collect_arg]))] - else: - cast = [Assign(arg_var, FunctionCall(pyarray_to_ndarray, [collect_arg]))] + cast = self._extract_FunctionDefArgument(orig_var, arg_var, collect_arg) if arg_var.is_optional and not isinstance(dtype, CustomDataType): memory_var = self.scope.get_temporary_variable(arg_var, name = arg_var.name + '_memory', is_optional = False) @@ -2128,3 +2103,70 @@ def _wrap_Import(self, expr): return Import(wrapper_name, AsName(mod_spoof, expr.source), mod = mod_spoof) else: return None + + def _extract_FunctionDefArgument(self, orig_var, arg_var, collect_arg): + class_type = orig_var.class_type + + classes = type(class_type).__mro__ + for cls in classes: + annotation_method = f'_extract_{cls.__name__}_FunctionDefArgument' + if hasattr(self, annotation_method): + return getattr(self, annotation_method)(orig_var, arg_var, collect_arg) + + # Unknown object, we raise an error. + return errors.report(PYCCEL_RESTRICTION_TODO, symbol=orig_var, + severity='fatal') + + def _extract_FixedSizeType_FunctionDefArgument(self, orig_var, arg_var, collect_arg): + dtype = orig_var.dtype + try : + cast_function = py_to_c_registry[(dtype.primitive_type, dtype.precision)] + except KeyError: + errors.report(PYCCEL_RESTRICTION_TODO, symbol=dtype,severity='fatal') + cast_func = FunctionDef(name = cast_function, + body = [], + arguments = [FunctionDefArgument(Variable(PyccelPyObject(), name = 'o', memory_handling='alias'))], + results = [FunctionDefResult(Variable(dtype, name = 'v'))]) + return [Assign(arg_var, FunctionCall(cast_func, [collect_arg]))] + + def _extract_CustomDataType_FunctionDefArgument(self, orig_var, arg_var, collect_arg): + dtype = orig_var.dtype + python_cls_base = self.scope.find(dtype.name, 'classes', raise_if_missing = True) + scope = python_cls_base.scope + attribute = scope.find('instance', 'variables', raise_if_missing = True) + if bound_argument: + cast_type = collect_arg + cast = [] + else: + cast_type = Variable(self._python_object_map[dtype], + self.scope.get_new_name(collect_arg.name), + memory_handling='alias', + cls_base = self.scope.find(dtype.name, 'classes', raise_if_missing = True)) + self.scope.insert_variable(cast_type) + cast = [AliasAssign(cast_type, PointerCast(collect_arg, cast_type))] + c_res = attribute.clone(attribute.name, new_class = DottedVariable, lhs = cast_type) + cast_c_res = PointerCast(c_res, orig_var) + cast.append(AliasAssign(arg_var, cast_c_res)) + return cast + + def _extract_NumpyNDArrayType_FunctionDefArgument(self, orig_var, arg_var, collect_arg): + return [Assign(arg_var, FunctionCall(pyarray_to_ndarray, [collect_arg]))] + + def _extract_HomogeneousTupleType_FunctionDefArgument(self, orig_var, arg_var, collect_arg): + idx = self.scope.get_temporary_variable(CNativeInt()) + size_var = self.scope.get_temporary_variable(PythonNativeInt(), f'{orig_var.name}_size') + indexed_orig_var = IndexedElement(orig_var, idx) + indexed_arg_var = IndexedElement(arg_var, idx) + indexed_collect_arg = self.scope.get_temporary_variable(PyccelPyObject(), memory_handling='alias') + + cast = [Assign(size_var, FunctionCall(PyTuple_Size, [collect_arg])), + Allocate(arg_var, shape = (size_var,), status = 'unallocated')] + + for_scope = self.scope.create_new_loop_scope() + self.scope = for_scope + for_body = [AliasAssign(indexed_collect_arg, FunctionCall(PyTuple_GetItem, [collect_arg, idx]))] + for_body += self._extract_FunctionDefArgument(indexed_orig_var, indexed_arg_var, indexed_collect_arg) + self.exit_scope() + + cast.append(For(idx, Iterable(PythonRange(size_var)), for_body, scope = for_scope)) + return cast From b1466d97cd8028fec74f9ef01f004cfc517a8183 Mon Sep 17 00:00:00 2001 From: Emily Bourne Date: Tue, 23 Apr 2024 22:23:42 +0200 Subject: [PATCH 03/28] Strides are only needed for NumpyNDArrayType --- pyccel/ast/bind_c.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pyccel/ast/bind_c.py b/pyccel/ast/bind_c.py index cb6a4bd09b..d5ca1d2d8b 100644 --- a/pyccel/ast/bind_c.py +++ b/pyccel/ast/bind_c.py @@ -13,6 +13,7 @@ from pyccel.ast.core import FunctionDef, ClassDef from pyccel.ast.core import FunctionDefArgument, FunctionDefResult from pyccel.ast.datatypes import FixedSizeType, PythonNativeInt +from pyccel.ast.numpytypes import NumpyNDArrayType from pyccel.ast.variable import Variable from pyccel.utilities.metaclasses import Singleton @@ -203,9 +204,12 @@ def __init__(self, var, scope, original_arg_var, wrapping_bound_argument, **kwar shape = [scope.get_temporary_variable(PythonNativeInt(), name=f'{name}_shape_{i+1}') for i in range(self._rank)] - strides = [scope.get_temporary_variable(PythonNativeInt(), - name=f'{name}_stride_{i+1}') - for i in range(self._rank)] + if isinstance(original_arg_var.class_type, NumpyNDArrayType): + strides = [scope.get_temporary_variable(PythonNativeInt(), + name=f'{name}_stride_{i+1}') + for i in range(self._rank)] + else: + strides = [] self._shape = shape self._strides = strides self._original_arg_var = original_arg_var From ca67c962f076770a925f640a4b45f1c7a4bfc779 Mon Sep 17 00:00:00 2001 From: Emily Bourne Date: Tue, 23 Apr 2024 22:25:37 +0200 Subject: [PATCH 04/28] Update condition --- pyccel/codegen/wrapper/fortran_to_c_wrapper.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pyccel/codegen/wrapper/fortran_to_c_wrapper.py b/pyccel/codegen/wrapper/fortran_to_c_wrapper.py index bd8050a274..e25c18b02d 100644 --- a/pyccel/codegen/wrapper/fortran_to_c_wrapper.py +++ b/pyccel/codegen/wrapper/fortran_to_c_wrapper.py @@ -17,9 +17,10 @@ from pyccel.ast.core import Allocate, EmptyNode, FunctionAddress from pyccel.ast.core import If, IfSection, Import, Interface, FunctionDefArgument from pyccel.ast.core import AsName, Module, AliasAssign, FunctionDefResult -from pyccel.ast.datatypes import CustomDataType, FixedSizeNumericType +from pyccel.ast.datatypes import CustomDataType, FixedSizeNumericType, HomogeneousTupleType from pyccel.ast.internals import Slice from pyccel.ast.literals import LiteralInteger, Nil, LiteralTrue +from pyccel.ast.numpytypes import NumpyNDArrayType from pyccel.ast.operators import PyccelIsNot, PyccelMul from pyccel.ast.variable import Variable, IndexedElement, DottedVariable from pyccel.parser.scope import Scope @@ -334,7 +335,8 @@ def _wrap_FunctionDefArgument(self, expr): name = var.name self.scope.insert_symbol(name) collisionless_name = self.scope.get_expected_name(var.name) - if var.is_ndarray or var.is_optional or isinstance(var.dtype, CustomDataType): + if isinstance(var.class_type, (NumpyNDArrayType, HomogeneousTupleType)) or \ + var.is_optional or isinstance(var.dtype, CustomDataType): new_var = Variable(BindCPointer(), self.scope.get_new_name(f'bound_{name}'), is_argument = True, is_optional = False, memory_handling='alias') arg_var = var.clone(collisionless_name, is_argument = False, is_optional = False, From 87ddfd768de219c5d25cb8649e1a456c7db33879 Mon Sep 17 00:00:00 2001 From: Emily Bourne Date: Tue, 23 Apr 2024 22:27:38 +0200 Subject: [PATCH 05/28] Update conditions --- pyccel/codegen/wrapper/c_to_python_wrapper.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyccel/codegen/wrapper/c_to_python_wrapper.py b/pyccel/codegen/wrapper/c_to_python_wrapper.py index 8171446a58..9f68c499b6 100644 --- a/pyccel/codegen/wrapper/c_to_python_wrapper.py +++ b/pyccel/codegen/wrapper/c_to_python_wrapper.py @@ -1422,10 +1422,10 @@ def _wrap_FunctionDefArgument(self, expr): orig_var = getattr(expr, 'original_function_argument_variable', expr.var) bound_argument = getattr(expr, 'wrapping_bound_argument', expr.bound_argument) - if orig_var.is_ndarray: + if isinstance(orig_var.class_type, (NumpyNDArrayType, HomogeneousTupleType)): arg_var = orig_var.clone(self.scope.get_expected_name(orig_var.name), is_argument = False, memory_handling='alias', new_class = Variable) - self._wrapping_arrays = orig_var.is_ndarray + self._wrapping_arrays = True self.scope.insert_variable(arg_var, orig_var.name) else: kwargs = {'is_argument':False} @@ -1497,7 +1497,7 @@ def _wrap_BindCFunctionDefArgument(self, expr): orig_var = expr.original_function_argument_variable - if orig_var.rank: + if isinstance(orig_var.class_type, (NumpyNDArrayType, HomogeneousTupleType)): bound_var_name = expr.var.name # Create variable to hold raw data pointer arg_var = expr.var.clone(self.scope.get_expected_name(bound_var_name), is_argument = False) From 5ec1f5fb556e421a6099504dfc52d5804d2ec73a Mon Sep 17 00:00:00 2001 From: Emily Bourne Date: Tue, 23 Apr 2024 22:44:35 +0200 Subject: [PATCH 06/28] Add docstrings --- pyccel/codegen/wrapper/c_to_python_wrapper.py | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/pyccel/codegen/wrapper/c_to_python_wrapper.py b/pyccel/codegen/wrapper/c_to_python_wrapper.py index 9f68c499b6..43b30fea8d 100644 --- a/pyccel/codegen/wrapper/c_to_python_wrapper.py +++ b/pyccel/codegen/wrapper/c_to_python_wrapper.py @@ -2105,6 +2105,39 @@ def _wrap_Import(self, expr): return None def _extract_FunctionDefArgument(self, orig_var, arg_var, collect_arg): + """ + Extract the C-compatible FunctionDefArgument from the PythonObject. + + Extract the C-compatible FunctionDefArgument from the PythonObject. + The C-compatible argument is extracted from collect_arg which holds a Python + oject into arg_var. + + The extraction is done by finding the appropriate function + _extract_X_FunctionDefArgument for the object expr. X is the class type of the + object expr. If this function does not exist then the method resolution order + is used to search for other compatible _extract_X_FunctionDefArgument functions. + If none are found then an error is raised. + + + Parameters + ---------- + orig_var : Variable | IndexedElement + An object representing the variable or an element of the variable from the + FunctionDefArgument being wrapped. + + arg_var : Variable | IndexedElement + A variable or an element of the variable representing the argument that + will be passed to the low-level function call. + + collect_arg : Variable + A variable with type PythonObject* holding the Python argument from which the + C-compatible argument should be collected. + + Returns + ------- + list[PyccelAstNode] + A list of expressions which extract the argument from collect_arg into arg_var. + """ class_type = orig_var.class_type classes = type(class_type).__mro__ @@ -2118,6 +2151,35 @@ def _extract_FunctionDefArgument(self, orig_var, arg_var, collect_arg): severity='fatal') def _extract_FixedSizeType_FunctionDefArgument(self, orig_var, arg_var, collect_arg): + """ + Extract the C-compatible scalar FunctionDefArgument from the PythonObject. + + Extract the C-compatible scalar FunctionDefArgument from the PythonObject. + The C-compatible argument is extracted from collect_arg which holds a Python + oject into arg_var. + + The extraction is done by calling a function from the C-Python API. These functions + are indexed in the dictionary `py_to_c_registry`. + + Parameters + ---------- + orig_var : Variable | IndexedElement + An object representing the variable or an element of the variable from the + FunctionDefArgument being wrapped. + + arg_var : Variable | IndexedElement + A variable or an element of the variable representing the argument that + will be passed to the low-level function call. + + collect_arg : Variable + A variable with type PythonObject* holding the Python argument from which the + C-compatible argument should be collected. + + Returns + ------- + list[PyccelAstNode] + A list of expressions which extract the argument from collect_arg into arg_var. + """ dtype = orig_var.dtype try : cast_function = py_to_c_registry[(dtype.primitive_type, dtype.precision)] @@ -2130,6 +2192,35 @@ def _extract_FixedSizeType_FunctionDefArgument(self, orig_var, arg_var, collect_ return [Assign(arg_var, FunctionCall(cast_func, [collect_arg]))] def _extract_CustomDataType_FunctionDefArgument(self, orig_var, arg_var, collect_arg): + """ + Extract the C-compatible class FunctionDefArgument from the PythonObject. + + Extract the C-compatible class FunctionDefArgument from the PythonObject. + The C-compatible argument is extracted from collect_arg which holds a Python + oject into arg_var. + + The extraction is done by accessing the pointer from the `instance` attribute of the + Pyccel generated class definition. + + Parameters + ---------- + orig_var : Variable | IndexedElement + An object representing the variable or an element of the variable from the + FunctionDefArgument being wrapped. + + arg_var : Variable | IndexedElement + A variable or an element of the variable representing the argument that + will be passed to the low-level function call. + + collect_arg : Variable + A variable with type PythonObject* holding the Python argument from which the + C-compatible argument should be collected. + + Returns + ------- + list[PyccelAstNode] + A list of expressions which extract the argument from collect_arg into arg_var. + """ dtype = orig_var.dtype python_cls_base = self.scope.find(dtype.name, 'classes', raise_if_missing = True) scope = python_cls_base.scope @@ -2150,9 +2241,66 @@ def _extract_CustomDataType_FunctionDefArgument(self, orig_var, arg_var, collect return cast def _extract_NumpyNDArrayType_FunctionDefArgument(self, orig_var, arg_var, collect_arg): + """ + Extract the C-compatible NumPy array FunctionDefArgument from the PythonObject. + + Extract the C-compatible NumPy array FunctionDefArgument from the PythonObject. + The C-compatible argument is extracted from collect_arg which holds a Python + oject into arg_var. + + The extraction is done by calling the function `pyarray_to_ndarray` from the stdlib. + + Parameters + ---------- + orig_var : Variable | IndexedElement + An object representing the variable or an element of the variable from the + FunctionDefArgument being wrapped. + + arg_var : Variable | IndexedElement + A variable or an element of the variable representing the argument that + will be passed to the low-level function call. + + collect_arg : Variable + A variable with type PythonObject* holding the Python argument from which the + C-compatible argument should be collected. + + Returns + ------- + list[PyccelAstNode] + A list of expressions which extract the argument from collect_arg into arg_var. + """ return [Assign(arg_var, FunctionCall(pyarray_to_ndarray, [collect_arg]))] def _extract_HomogeneousTupleType_FunctionDefArgument(self, orig_var, arg_var, collect_arg): + """ + Extract the C-compatible homogeneous tuple FunctionDefArgument from the PythonObject. + + Extract the C-compatible homogeneous tuple FunctionDefArgument from the PythonObject. + The C-compatible argument is extracted from collect_arg which holds a Python + oject into arg_var. + + The extraction is done by allocating an array and filling the elements with values + extracted from the indexed Python tuple in collect_arg. + + Parameters + ---------- + orig_var : Variable | IndexedElement + An object representing the variable or an element of the variable from the + FunctionDefArgument being wrapped. + + arg_var : Variable | IndexedElement + A variable or an element of the variable representing the argument that + will be passed to the low-level function call. + + collect_arg : Variable + A variable with type PythonObject* holding the Python argument from which the + C-compatible argument should be collected. + + Returns + ------- + list[PyccelAstNode] + A list of expressions which extract the argument from collect_arg into arg_var. + """ idx = self.scope.get_temporary_variable(CNativeInt()) size_var = self.scope.get_temporary_variable(PythonNativeInt(), f'{orig_var.name}_size') indexed_orig_var = IndexedElement(orig_var, idx) From 17add1a975e92b68bae4df72f213d19cb873d7ec Mon Sep 17 00:00:00 2001 From: Emily Bourne Date: Tue, 23 Apr 2024 22:59:53 +0200 Subject: [PATCH 07/28] Add bound_argument to args --- pyccel/codegen/wrapper/c_to_python_wrapper.py | 37 +++++++++++++++---- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/pyccel/codegen/wrapper/c_to_python_wrapper.py b/pyccel/codegen/wrapper/c_to_python_wrapper.py index 43b30fea8d..1ed59fa97d 100644 --- a/pyccel/codegen/wrapper/c_to_python_wrapper.py +++ b/pyccel/codegen/wrapper/c_to_python_wrapper.py @@ -2104,7 +2104,7 @@ def _wrap_Import(self, expr): else: return None - def _extract_FunctionDefArgument(self, orig_var, arg_var, collect_arg): + def _extract_FunctionDefArgument(self, orig_var, arg_var, collect_arg, bound_argument): """ Extract the C-compatible FunctionDefArgument from the PythonObject. @@ -2133,6 +2133,10 @@ def _extract_FunctionDefArgument(self, orig_var, arg_var, collect_arg): A variable with type PythonObject* holding the Python argument from which the C-compatible argument should be collected. + bound_argument : bool + True if the argument is the self argument of a class method. False otherwise. + This should always be False for this function. + Returns ------- list[PyccelAstNode] @@ -2144,13 +2148,13 @@ def _extract_FunctionDefArgument(self, orig_var, arg_var, collect_arg): for cls in classes: annotation_method = f'_extract_{cls.__name__}_FunctionDefArgument' if hasattr(self, annotation_method): - return getattr(self, annotation_method)(orig_var, arg_var, collect_arg) + return getattr(self, annotation_method)(orig_var, arg_var, collect_arg, bound_argument) # Unknown object, we raise an error. return errors.report(PYCCEL_RESTRICTION_TODO, symbol=orig_var, severity='fatal') - def _extract_FixedSizeType_FunctionDefArgument(self, orig_var, arg_var, collect_arg): + def _extract_FixedSizeType_FunctionDefArgument(self, orig_var, arg_var, collect_arg, bound_argument): """ Extract the C-compatible scalar FunctionDefArgument from the PythonObject. @@ -2175,11 +2179,16 @@ def _extract_FixedSizeType_FunctionDefArgument(self, orig_var, arg_var, collect_ A variable with type PythonObject* holding the Python argument from which the C-compatible argument should be collected. + bound_argument : bool + True if the argument is the self argument of a class method. False otherwise. + This should always be False for this function. + Returns ------- list[PyccelAstNode] A list of expressions which extract the argument from collect_arg into arg_var. """ + assert not bound_argument dtype = orig_var.dtype try : cast_function = py_to_c_registry[(dtype.primitive_type, dtype.precision)] @@ -2191,7 +2200,7 @@ def _extract_FixedSizeType_FunctionDefArgument(self, orig_var, arg_var, collect_ results = [FunctionDefResult(Variable(dtype, name = 'v'))]) return [Assign(arg_var, FunctionCall(cast_func, [collect_arg]))] - def _extract_CustomDataType_FunctionDefArgument(self, orig_var, arg_var, collect_arg): + def _extract_CustomDataType_FunctionDefArgument(self, orig_var, arg_var, collect_arg, bound_argument): """ Extract the C-compatible class FunctionDefArgument from the PythonObject. @@ -2216,6 +2225,10 @@ def _extract_CustomDataType_FunctionDefArgument(self, orig_var, arg_var, collect A variable with type PythonObject* holding the Python argument from which the C-compatible argument should be collected. + bound_argument : bool + True if the argument is the self argument of a class method. False otherwise. + This should always be False for this function. + Returns ------- list[PyccelAstNode] @@ -2240,7 +2253,7 @@ def _extract_CustomDataType_FunctionDefArgument(self, orig_var, arg_var, collect cast.append(AliasAssign(arg_var, cast_c_res)) return cast - def _extract_NumpyNDArrayType_FunctionDefArgument(self, orig_var, arg_var, collect_arg): + def _extract_NumpyNDArrayType_FunctionDefArgument(self, orig_var, arg_var, collect_arg, bound_argument): """ Extract the C-compatible NumPy array FunctionDefArgument from the PythonObject. @@ -2264,14 +2277,19 @@ def _extract_NumpyNDArrayType_FunctionDefArgument(self, orig_var, arg_var, colle A variable with type PythonObject* holding the Python argument from which the C-compatible argument should be collected. + bound_argument : bool + True if the argument is the self argument of a class method. False otherwise. + This should always be False for this function. + Returns ------- list[PyccelAstNode] A list of expressions which extract the argument from collect_arg into arg_var. """ + assert not bound_argument return [Assign(arg_var, FunctionCall(pyarray_to_ndarray, [collect_arg]))] - def _extract_HomogeneousTupleType_FunctionDefArgument(self, orig_var, arg_var, collect_arg): + def _extract_HomogeneousTupleType_FunctionDefArgument(self, orig_var, arg_var, collect_arg, bound_argument): """ Extract the C-compatible homogeneous tuple FunctionDefArgument from the PythonObject. @@ -2296,11 +2314,16 @@ def _extract_HomogeneousTupleType_FunctionDefArgument(self, orig_var, arg_var, c A variable with type PythonObject* holding the Python argument from which the C-compatible argument should be collected. + bound_argument : bool + True if the argument is the self argument of a class method. False otherwise. + This should always be False for this function. + Returns ------- list[PyccelAstNode] A list of expressions which extract the argument from collect_arg into arg_var. """ + assert not bound_argument idx = self.scope.get_temporary_variable(CNativeInt()) size_var = self.scope.get_temporary_variable(PythonNativeInt(), f'{orig_var.name}_size') indexed_orig_var = IndexedElement(orig_var, idx) @@ -2313,7 +2336,7 @@ def _extract_HomogeneousTupleType_FunctionDefArgument(self, orig_var, arg_var, c for_scope = self.scope.create_new_loop_scope() self.scope = for_scope for_body = [AliasAssign(indexed_collect_arg, FunctionCall(PyTuple_GetItem, [collect_arg, idx]))] - for_body += self._extract_FunctionDefArgument(indexed_orig_var, indexed_arg_var, indexed_collect_arg) + for_body += self._extract_FunctionDefArgument(indexed_orig_var, indexed_arg_var, indexed_collect_arg, bound_argument) self.exit_scope() cast.append(For(idx, Iterable(PythonRange(size_var)), for_body, scope = for_scope)) From 820f4e39bf5d21f66f9ebc4788f76e1176b4bf22 Mon Sep 17 00:00:00 2001 From: Emily Bourne Date: Tue, 23 Apr 2024 23:00:13 +0200 Subject: [PATCH 08/28] Unused import --- pyccel/codegen/wrapper/c_to_python_wrapper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyccel/codegen/wrapper/c_to_python_wrapper.py b/pyccel/codegen/wrapper/c_to_python_wrapper.py index 1ed59fa97d..64b61c05ec 100644 --- a/pyccel/codegen/wrapper/c_to_python_wrapper.py +++ b/pyccel/codegen/wrapper/c_to_python_wrapper.py @@ -29,7 +29,7 @@ from pyccel.ast.cwrapper import PyList_New, PyList_Append, PyList_GetItem, PyList_SetItem from pyccel.ast.cwrapper import PyccelPyTypeObject, PyCapsule_New, PyCapsule_Import from pyccel.ast.cwrapper import PySys_GetObject, PyUnicode_FromString, PyGetSetDefElement -from pyccel.ast.cwrapper import PyTuple_Size, PyTuple_Check, PyTuple_New, PyTuple_GetItem +from pyccel.ast.cwrapper import PyTuple_Size, PyTuple_Check, PyTuple_GetItem from pyccel.ast.c_concepts import ObjectAddress, PointerCast, CStackArray, CNativeInt from pyccel.ast.datatypes import VoidType, PythonNativeInt, CustomDataType, DataTypeFactory from pyccel.ast.datatypes import FixedSizeNumericType, HomogeneousTupleType From 83d5a65478505b715b9fe0e322e88a2a6abaecbf Mon Sep 17 00:00:00 2001 From: Emily Bourne Date: Tue, 23 Apr 2024 23:54:17 +0200 Subject: [PATCH 09/28] Add tests --- tests/epyccel/test_tuples.py | 43 ++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/epyccel/test_tuples.py b/tests/epyccel/test_tuples.py index 9f21bb2ad7..62c3734477 100644 --- a/tests/epyccel/test_tuples.py +++ b/tests/epyccel/test_tuples.py @@ -103,3 +103,46 @@ def test_tuples_with_2d_args(test_func, language): f2(pyccel_x) np.allclose(python_x, pyccel_x) +def test_homogeneous_tuples_of_bools_as_args(language): + def my_tuple(a : 'tuple[bool,...]'): + return len(a), a[0], a[1], a[2] + + epyc_func = epyccel(my_tuple, language=language) + assert my_tuple((True, False, False)) == epyc_func((True, False, False)) + tuple_arg = (False, True, False, True, True, True) + assert my_tuple(tuple_arg) == epyc_func(tuple_arg) + +def test_homogeneous_tuples_of_ints_as_args(language): + def my_tuple(a : 'tuple[int,...]'): + return len(a), a[0], a[1], a[2] + + epyc_func = epyccel(my_tuple, language=language) + assert my_tuple((1,2,3)) == epyc_func((1,2,3)) + tuple_arg = (-1, 9, 20, -55, 23) + assert my_tuple(tuple_arg) == epyc_func(tuple_arg) + +def test_homogeneous_tuples_of_floats_as_args(language): + def my_tuple(a : 'tuple[float,...]'): + return len(a), a[0], a[1], a[2] + + epyc_func = epyccel(my_tuple, language=language) + assert my_tuple((1.0,2.0,3.0)) == epyc_func((1.0,2.0,3.0)) + tuple_arg = (-1.0, 9.0, 20.0, -55.3, 23.2) + assert my_tuple(tuple_arg) == epyc_func(tuple_arg) + +def test_homogeneous_tuples_of_complexes_as_args(language): + def my_tuple(a : 'tuple[complex,...]'): + return len(a), a[0], a[1], a[2] + + epyc_func = epyccel(my_tuple, language=language) + assert my_tuple((1.0+4j, 2.0-2j, 3.0+0j)) == epyc_func((1.0+4j, 2.0-2j, 3.0+0j)) + tuple_arg = (1.0+4j, 2.0-2j, 3.0+0j, -23.12-4.4j) + assert my_tuple(tuple_arg) == epyc_func(tuple_arg) + +def test_homogeneous_tuples_of_numpy_ints_as_args(language): + def my_tuple(a : 'tuple[int8,...]'): + return len(a), a[0], a[1], a[2] + + epyc_func = epyccel(my_tuple, language=language) + tuple_arg = (np.int8(1), np.int8(2), np.int8(3)) + assert my_tuple(tuple_arg) == epyc_func(tuple_arg) From 108bc28d39eaaaf1d52b1c74ada488f7cadbba5f Mon Sep 17 00:00:00 2001 From: Emily Bourne Date: Tue, 23 Apr 2024 23:55:00 +0200 Subject: [PATCH 10/28] Fix call --- pyccel/codegen/wrapper/c_to_python_wrapper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyccel/codegen/wrapper/c_to_python_wrapper.py b/pyccel/codegen/wrapper/c_to_python_wrapper.py index 64b61c05ec..293bd9e8b7 100644 --- a/pyccel/codegen/wrapper/c_to_python_wrapper.py +++ b/pyccel/codegen/wrapper/c_to_python_wrapper.py @@ -1450,7 +1450,7 @@ def _wrap_FunctionDefArgument(self, expr): # Collect the function which casts from a Python object to a C object dtype = orig_var.dtype - cast = self._extract_FunctionDefArgument(orig_var, arg_var, collect_arg) + cast = self._extract_FunctionDefArgument(orig_var, arg_var, collect_arg, bound_argument) if arg_var.is_optional and not isinstance(dtype, CustomDataType): memory_var = self.scope.get_temporary_variable(arg_var, name = arg_var.name + '_memory', is_optional = False) From 67971970a926ac88bc358dcfe8a9218d89357192 Mon Sep 17 00:00:00 2001 From: Emily Bourne Date: Wed, 24 Apr 2024 00:05:40 +0200 Subject: [PATCH 11/28] Correct memory handling --- pyccel/codegen/wrapper/c_to_python_wrapper.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pyccel/codegen/wrapper/c_to_python_wrapper.py b/pyccel/codegen/wrapper/c_to_python_wrapper.py index 293bd9e8b7..fad9b1c52e 100644 --- a/pyccel/codegen/wrapper/c_to_python_wrapper.py +++ b/pyccel/codegen/wrapper/c_to_python_wrapper.py @@ -1422,11 +1422,16 @@ def _wrap_FunctionDefArgument(self, expr): orig_var = getattr(expr, 'original_function_argument_variable', expr.var) bound_argument = getattr(expr, 'wrapping_bound_argument', expr.bound_argument) - if isinstance(orig_var.class_type, (NumpyNDArrayType, HomogeneousTupleType)): + if isinstance(orig_var.class_type, NumpyNDArrayType): arg_var = orig_var.clone(self.scope.get_expected_name(orig_var.name), is_argument = False, memory_handling='alias', new_class = Variable) self._wrapping_arrays = True self.scope.insert_variable(arg_var, orig_var.name) + elif isinstance(orig_var.class_type, HomogeneousTupleType): + arg_var = orig_var.clone(self.scope.get_expected_name(orig_var.name), is_argument = False, + memory_handling='heap', new_class = Variable) + self._wrapping_arrays = True + self.scope.insert_variable(arg_var, orig_var.name) else: kwargs = {'is_argument':False} if isinstance(orig_var.dtype, CustomDataType): From d081016ca0a9d5d058628d32d693a246ebe42837 Mon Sep 17 00:00:00 2001 From: Emily Bourne Date: Wed, 24 Apr 2024 00:16:53 +0200 Subject: [PATCH 12/28] Fix Bind-C unpacking --- pyccel/codegen/wrapper/fortran_to_c_wrapper.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyccel/codegen/wrapper/fortran_to_c_wrapper.py b/pyccel/codegen/wrapper/fortran_to_c_wrapper.py index e25c18b02d..76ce23868d 100644 --- a/pyccel/codegen/wrapper/fortran_to_c_wrapper.py +++ b/pyccel/codegen/wrapper/fortran_to_c_wrapper.py @@ -95,6 +95,9 @@ def _get_function_def_body(self, func, func_def_args, func_arg_to_call_arg, resu body = [C_F_Pointer(fa.var, func_arg_to_call_arg[fa].base, s) for fa,s in zip(func_def_args, orig_size) if isinstance(func_arg_to_call_arg[fa], IndexedElement)] + body += [C_F_Pointer(fa.var, func_arg_to_call_arg[fa], [fa.shape[0]]) + for fa in func_def_args + if isinstance(fa.original_function_argument_variable.class_type, HomogeneousTupleType)] body += [C_F_Pointer(fa.var, func_arg_to_call_arg[fa]) for fa in func_def_args if not isinstance(func_arg_to_call_arg[fa], IndexedElement) \ From c65f51650ba82a30eab37105e8ba18dee2bad34e Mon Sep 17 00:00:00 2001 From: EmilyBourne Date: Wed, 24 Apr 2024 09:53:04 +0200 Subject: [PATCH 13/28] Extra space in docstring --- pyccel/codegen/wrapper/c_to_python_wrapper.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyccel/codegen/wrapper/c_to_python_wrapper.py b/pyccel/codegen/wrapper/c_to_python_wrapper.py index fad9b1c52e..b1c2a6230f 100644 --- a/pyccel/codegen/wrapper/c_to_python_wrapper.py +++ b/pyccel/codegen/wrapper/c_to_python_wrapper.py @@ -2123,7 +2123,6 @@ def _extract_FunctionDefArgument(self, orig_var, arg_var, collect_arg, bound_arg is used to search for other compatible _extract_X_FunctionDefArgument functions. If none are found then an error is raised. - Parameters ---------- orig_var : Variable | IndexedElement From 931f3a7880569d105fd1c51cf7938103bba84834 Mon Sep 17 00:00:00 2001 From: EmilyBourne Date: Thu, 2 May 2024 20:19:23 +0200 Subject: [PATCH 14/28] Allow searching in a SyntacticTypeAnnotation --- pyccel/ast/type_annotations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyccel/ast/type_annotations.py b/pyccel/ast/type_annotations.py index 7a70c6c1c5..77fcc30a12 100644 --- a/pyccel/ast/type_annotations.py +++ b/pyccel/ast/type_annotations.py @@ -262,7 +262,7 @@ class SyntacticTypeAnnotation(PyccelAstNode): The order requested in the type annotation. """ __slots__ = ('_dtype', '_order') - _attribute_nodes = () + _attribute_nodes = ('_dtype',) def __init__(self, dtype, order = None): if not isinstance(dtype, (str, DottedName, IndexedElement)): raise ValueError("Syntactic datatypes should be strings") From 674de412ac8dade39a546e8193edbfef65e2d90a Mon Sep 17 00:00:00 2001 From: EmilyBourne Date: Thu, 2 May 2024 20:19:35 +0200 Subject: [PATCH 15/28] Generalise used_type_names calculation --- pyccel/parser/semantic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyccel/parser/semantic.py b/pyccel/parser/semantic.py index 19adcf7604..2e0a1e7dcf 100644 --- a/pyccel/parser/semantic.py +++ b/pyccel/parser/semantic.py @@ -3771,7 +3771,7 @@ def unpack(ann): templatable_args = [unpack(a.annotation) for a in expr.arguments if isinstance(a.annotation, (SyntacticTypeAnnotation, UnionTypeAnnotation))] arg_annotations = [annot for a in templatable_args for annot in a if isinstance(annot, SyntacticTypeAnnotation)] type_names = [a.dtype for a in arg_annotations] - used_type_names = set(d.base if isinstance(d, IndexedElement) else d for d in type_names) + used_type_names = set(d for t in type_names for d in t.get_attribute_nodes(PyccelSymbol)) templates = {t: v for t,v in templates.items() if t in used_type_names} # Create new temparary templates for the arguments with a Union data type. From 86416ffa876dcb42a93f2caab0223449309dc516 Mon Sep 17 00:00:00 2001 From: EmilyBourne Date: Thu, 2 May 2024 21:17:10 +0200 Subject: [PATCH 16/28] Generalise type check --- pyccel/codegen/wrapper/c_to_python_wrapper.py | 55 ++++++++++++++----- 1 file changed, 41 insertions(+), 14 deletions(-) diff --git a/pyccel/codegen/wrapper/c_to_python_wrapper.py b/pyccel/codegen/wrapper/c_to_python_wrapper.py index b1c2a6230f..5a11316401 100644 --- a/pyccel/codegen/wrapper/c_to_python_wrapper.py +++ b/pyccel/codegen/wrapper/c_to_python_wrapper.py @@ -32,7 +32,7 @@ from pyccel.ast.cwrapper import PyTuple_Size, PyTuple_Check, PyTuple_GetItem from pyccel.ast.c_concepts import ObjectAddress, PointerCast, CStackArray, CNativeInt from pyccel.ast.datatypes import VoidType, PythonNativeInt, CustomDataType, DataTypeFactory -from pyccel.ast.datatypes import FixedSizeNumericType, HomogeneousTupleType +from pyccel.ast.datatypes import FixedSizeNumericType, HomogeneousTupleType, PythonNativeBool from pyccel.ast.literals import Nil, LiteralTrue, LiteralString, LiteralInteger from pyccel.ast.literals import LiteralFalse from pyccel.ast.numpyext import NumpyNDArrayType @@ -42,7 +42,7 @@ from pyccel.ast.numpy_wrapper import numpy_dtype_registry, numpy_flag_f_contig, numpy_flag_c_contig from pyccel.ast.numpy_wrapper import pyarray_check, is_numpy_array, no_order_check from pyccel.ast.operators import PyccelNot, PyccelIsNot, PyccelUnarySub, PyccelEq, PyccelIs -from pyccel.ast.operators import PyccelLt, IfTernaryOperator +from pyccel.ast.operators import PyccelLt, IfTernaryOperator, PyccelAnd from pyccel.ast.variable import Variable, DottedVariable, IndexedElement from pyccel.parser.scope import Scope from pyccel.errors.errors import Errors @@ -237,9 +237,9 @@ def _get_python_result_variables(self, results): self._python_object_map.update(dict(zip(results, collect_results))) return collect_results - def _get_check_function(self, py_obj, arg, raise_error): + def _get_type_check_condition(self, py_obj, arg, raise_error, body): """ - Get the function which checks if an argument has the expected type. + Get the condition which checks if an argument has the expected type. Using the c-compatible description of a function argument, determine whether the Python object (with datatype `PyccelPyObject`) holds data which is compatible with the expected @@ -257,10 +257,16 @@ def _get_check_function(self, py_obj, arg, raise_error): raise_error : bool True if an error should be raised in case of an unexpected type, False otherwise. + body : list + A list describing code where the type check will occur. This allows any necessary code + to be inserted into the code block. E.g. code which should be run before the condition + can be checked. + Returns ------- - func_call : FunctionCall - The function call which checks if the argument has the expected type. + type_check_condition : FunctionCall | Variable + The function call which checks if the argument has the expected type or the variable + indicating if the argument has the expected type. error_code : tuple of pyccel.ast.basic.PyccelAstNode The code which raises any necessary errors. @@ -270,7 +276,7 @@ def _get_check_function(self, py_obj, arg, raise_error): dtype = arg.dtype if isinstance(dtype, CustomDataType): python_cls_base = self.scope.find(dtype.name, 'classes', raise_if_missing = True) - func_call = FunctionCall(PyObject_TypeCheck, [py_obj, python_cls_base.type_object]) + type_check_condition = FunctionCall(PyObject_TypeCheck, [py_obj, python_cls_base.type_object]) elif rank == 0: try : cast_function = check_type_registry[dtype] @@ -282,7 +288,7 @@ def _get_check_function(self, py_obj, arg, raise_error): arguments = [FunctionDefArgument(Variable(PyccelPyObject(), name = 'o', memory_handling='alias'))], results = [FunctionDefResult(Variable(dtype, name = 'v'))]) - func_call = FunctionCall(func, [py_obj]) + type_check_condition = FunctionCall(func, [py_obj]) elif isinstance(arg.class_type, NumpyNDArrayType): try : type_ref = numpy_dtype_registry[dtype] @@ -301,9 +307,30 @@ def _get_check_function(self, py_obj, arg, raise_error): check_func = pyarray_check if raise_error else is_numpy_array # No error code required as the error is raised inside pyarray_check - func_call = FunctionCall(check_func, [py_obj, type_ref, LiteralInteger(rank), flag]) + type_check_condition = FunctionCall(check_func, [py_obj, type_ref, LiteralInteger(rank), flag]) elif isinstance(arg.class_type, HomogeneousTupleType): - func_call = FunctionCall(PyTuple_Check, [py_obj]) + # Create type check result variable + type_check_condition = self.scope.get_temporary_variable(PythonNativeBool(), 'is_homog_tuple') + + # Check if the object is a tuple + tuple_check = FunctionCall(PyTuple_Check, [py_obj]) + + # If the tuple is an object check that the elements have the right type + for_scope = self.scope.create_new_loop_scope() + size_var = self.scope.get_temporary_variable(PythonNativeInt(), f'size') + idx = self.scope.get_temporary_variable(CNativeInt()) + indexed_py_obj = self.scope.get_temporary_variable(PyccelPyObject(), memory_handling='alias') + + indexed_init = AliasAssign(indexed_py_obj, FunctionCall(PyTuple_GetItem, [py_obj, idx])) + size_assign = Assign(size_var, FunctionCall(PyTuple_Size, [py_obj])) + for_body = [indexed_init] + internal_type_check_condition, _ = self._get_type_check_condition(indexed_py_obj, arg[0], False, for_body) + for_body.append(Assign(type_check_condition, PyccelAnd(type_check_condition, internal_type_check_condition))) + internal_type_check = For(idx, Iterable(PythonRange(size_var)), for_body, scope = for_scope) + + tuple_checks = IfSection(tuple_check, [size_assign, Assign(type_check_condition, LiteralTrue()), internal_type_check]) + default_value = IfSection(LiteralTrue(), [Assign(type_check_condition, LiteralFalse())]) + body.append(If(tuple_checks, default_value)) else: errors.report(f"Can't check the type of an array of {arg.class_type}\n"+PYCCEL_RESTRICTION_TODO, symbol=arg, severity='fatal') @@ -313,7 +340,7 @@ def _get_check_function(self, py_obj, arg, raise_error): python_error = FunctionCall(PyErr_SetString, [PyTypeError, message]) error_code = (python_error,) - return func_call, error_code + return type_check_condition, error_code def _get_type_check_function(self, name, args, funcs): """ @@ -399,7 +426,7 @@ def f(a, b): # Create the type checks and incrementation of the type_indicator if_blocks = [] for index, t in enumerate(possible_types): - check_func_call, _ = self._get_check_function(py_arg, type_to_example_arg[t], False) + check_func_call, _ = self._get_type_check_condition(py_arg, type_to_example_arg[t], False, body) if_blocks.append(IfSection(check_func_call, [AugAssign(type_indicator, '+', LiteralInteger(index*step))])) body.append(If(*if_blocks, IfSection(LiteralTrue(), [FunctionCall(PyErr_SetString, [PyTypeError, f"Unexpected type for argument {interface_args[0].name}"]), @@ -1463,12 +1490,12 @@ def _wrap_FunctionDefArgument(self, expr): # Create any necessary type checks and errors if expr.has_default: - check_func, err = self._get_check_function(collect_arg, orig_var, False) + check_func, err = self._get_type_check_condition(collect_arg, orig_var, False, body) body.append(If( IfSection(check_func, cast), IfSection(PyccelIsNot(collect_arg, Py_None), [*err, Return([self._error_exit_code])]) )) elif not (in_interface or bound_argument): - check_func, err = self._get_check_function(collect_arg, orig_var, True) + check_func, err = self._get_type_check_condition(collect_arg, orig_var, True, body) body.append(If( IfSection(check_func, cast), IfSection(LiteralTrue(), [*err, Return([self._error_exit_code])]) )) From 47a2939a7a64e42f512f70a961a7d8924e7d53fc Mon Sep 17 00:00:00 2001 From: EmilyBourne Date: Thu, 2 May 2024 21:17:17 +0200 Subject: [PATCH 17/28] Add templated test --- tests/epyccel/test_tuples.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/tests/epyccel/test_tuples.py b/tests/epyccel/test_tuples.py index 62c3734477..fbbc47295f 100644 --- a/tests/epyccel/test_tuples.py +++ b/tests/epyccel/test_tuples.py @@ -3,9 +3,11 @@ import pytest import numpy as np -from pyccel.epyccel import epyccel from modules import tuples as tuples_module +from pyccel.decorators import template +from pyccel.epyccel import epyccel + def is_func_with_0_args(f): """ Test if name 'f' corresponds to an argument in the tuples module with no arguments @@ -146,3 +148,22 @@ def my_tuple(a : 'tuple[int8,...]'): epyc_func = epyccel(my_tuple, language=language) tuple_arg = (np.int8(1), np.int8(2), np.int8(3)) assert my_tuple(tuple_arg) == epyc_func(tuple_arg) + +def test_homogeneous_tuples_template_args(language): + @template('T', [int, float]) + def my_tuple(a : 'tuple[T,...]'): + return len(a), a[0], a[1], a[2] + + epyc_func = epyccel(my_tuple, language=language) + tuple_int_arg = (1, 2, 3) + tuple_float_arg = (4., 5., 6.) + + int_pyth = my_tuple(tuple_int_arg) + int_epyc = my_tuple(tuple_int_arg) + assert int_pyth == int_epyc + assert isinstance(int_epyc[1], int) + + float_pyth = my_tuple(tuple_float_arg) + float_epyc = my_tuple(tuple_float_arg) + assert float_pyth == float_epyc + assert isinstance(float_epyc[1], float) From 2e83a0ac61b727833c8b371a146b7c12034d163b Mon Sep 17 00:00:00 2001 From: EmilyBourne Date: Thu, 2 May 2024 21:24:31 +0200 Subject: [PATCH 18/28] Add a failing multi-level tuple test --- tests/epyccel/test_tuples.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/epyccel/test_tuples.py b/tests/epyccel/test_tuples.py index fbbc47295f..b3e671697e 100644 --- a/tests/epyccel/test_tuples.py +++ b/tests/epyccel/test_tuples.py @@ -6,6 +6,7 @@ from modules import tuples as tuples_module from pyccel.decorators import template +from pyccel.errors.errors import PyccelError from pyccel.epyccel import epyccel def is_func_with_0_args(f): @@ -167,3 +168,17 @@ def my_tuple(a : 'tuple[T,...]'): float_epyc = my_tuple(tuple_float_arg) assert float_pyth == float_epyc assert isinstance(float_epyc[1], float) + +def test_multi_level_tuple_arg(language): + def my_tuple(a : 'tuple[tuple[int,...],...]'): + return len(a), len(a[0]), a[0][0], a[1][0], a[0][1], a[1][1] + + tuple_arg = ((1,2), (3,4)) + + # Raises an error because tuples inside tuples may have different lengths + # This could be removed once lists are supported as the tuples could then + # be stored in lists instead of arrays. + with pytest.raises(PyccelError): + epyc_func = epyccel(my_tuple, language=language) + + #assert my_tuple(tuple_arg) == epyc_func(tuple_arg) From b6a03721b27ce3cc42e7f4d725d5b4234b782a62 Mon Sep 17 00:00:00 2001 From: EmilyBourne Date: Thu, 2 May 2024 21:24:54 +0200 Subject: [PATCH 19/28] Raise an error for multi-level tuples --- pyccel/codegen/wrapper/c_to_python_wrapper.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyccel/codegen/wrapper/c_to_python_wrapper.py b/pyccel/codegen/wrapper/c_to_python_wrapper.py index 5a11316401..710c353146 100644 --- a/pyccel/codegen/wrapper/c_to_python_wrapper.py +++ b/pyccel/codegen/wrapper/c_to_python_wrapper.py @@ -2354,6 +2354,10 @@ def _extract_HomogeneousTupleType_FunctionDefArgument(self, orig_var, arg_var, c list[PyccelAstNode] A list of expressions which extract the argument from collect_arg into arg_var. """ + if arg_var.rank > 1: + errors.report("Wrapping multi-level tuples is not yet supported", + severity='fatal', symbol=arg_var) + assert not bound_argument idx = self.scope.get_temporary_variable(CNativeInt()) size_var = self.scope.get_temporary_variable(PythonNativeInt(), f'{orig_var.name}_size') From 738c264f48c542515aba25e698c44a1b52a3c6ed Mon Sep 17 00:00:00 2001 From: EmilyBourne Date: Thu, 2 May 2024 21:31:48 +0200 Subject: [PATCH 20/28] Pylint f-string --- pyccel/codegen/wrapper/c_to_python_wrapper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyccel/codegen/wrapper/c_to_python_wrapper.py b/pyccel/codegen/wrapper/c_to_python_wrapper.py index 710c353146..60aec1679e 100644 --- a/pyccel/codegen/wrapper/c_to_python_wrapper.py +++ b/pyccel/codegen/wrapper/c_to_python_wrapper.py @@ -317,7 +317,7 @@ def _get_type_check_condition(self, py_obj, arg, raise_error, body): # If the tuple is an object check that the elements have the right type for_scope = self.scope.create_new_loop_scope() - size_var = self.scope.get_temporary_variable(PythonNativeInt(), f'size') + size_var = self.scope.get_temporary_variable(PythonNativeInt(), 'size') idx = self.scope.get_temporary_variable(CNativeInt()) indexed_py_obj = self.scope.get_temporary_variable(PyccelPyObject(), memory_handling='alias') From aad2838c3c50bf3784e9a4a2b4089a27e6f665c0 Mon Sep 17 00:00:00 2001 From: EmilyBourne Date: Thu, 2 May 2024 21:32:27 +0200 Subject: [PATCH 21/28] Pylint errors and fix test --- tests/epyccel/test_tuples.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/epyccel/test_tuples.py b/tests/epyccel/test_tuples.py index a1f91e3d1d..9491c93723 100644 --- a/tests/epyccel/test_tuples.py +++ b/tests/epyccel/test_tuples.py @@ -160,12 +160,12 @@ def my_tuple(a : 'tuple[T,...]'): tuple_float_arg = (4., 5., 6.) int_pyth = my_tuple(tuple_int_arg) - int_epyc = my_tuple(tuple_int_arg) + int_epyc = epyc_func(tuple_int_arg) assert int_pyth == int_epyc assert isinstance(int_epyc[1], int) float_pyth = my_tuple(tuple_float_arg) - float_epyc = my_tuple(tuple_float_arg) + float_epyc = epyc_func(tuple_float_arg) assert float_pyth == float_epyc assert isinstance(float_epyc[1], float) @@ -173,12 +173,12 @@ def test_multi_level_tuple_arg(language): def my_tuple(a : 'tuple[tuple[int,...],...]'): return len(a), len(a[0]), a[0][0], a[1][0], a[0][1], a[1][1] - tuple_arg = ((1,2), (3,4)) + #tuple_arg = ((1,2), (3,4)) # Raises an error because tuples inside tuples may have different lengths # This could be removed once lists are supported as the tuples could then # be stored in lists instead of arrays. with pytest.raises(PyccelError): - epyc_func = epyccel(my_tuple, language=language) + _ = epyccel(my_tuple, language=language) #assert my_tuple(tuple_arg) == epyc_func(tuple_arg) From 5e5abe8feb7ba1245b669ba7802a8d980211573e Mon Sep 17 00:00:00 2001 From: EmilyBourne Date: Thu, 2 May 2024 21:34:28 +0200 Subject: [PATCH 22/28] CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cfff96a0e..fe4b33e368 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ All notable changes to this project will be documented in this file. - #1656 : Ensure `gFTL` is installed with Pyccel. - #1830 : Add a `pyccel.lambdify.lambdify` function to accelerate SymPy expressions. - #1844 : Add line numbers and code to errors from built-in function calls. +- #738 : Add support for homogeneous tuples with scalar elements as arguments. - \[INTERNALS\] Added `container_rank` property to `ast.datatypes.PyccelType` objects. - \[DEVELOPER\] Added an improved traceback to the developer-mode errors for errors in function calls. From a2dd1c6a8b4bea864ef7ed96b6fb3b61fdfbbcd3 Mon Sep 17 00:00:00 2001 From: EmilyBourne Date: Thu, 2 May 2024 21:37:33 +0200 Subject: [PATCH 23/28] Update doc --- docs/type_annotations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/type_annotations.md b/docs/type_annotations.md index c37b3f22ab..9e2015630d 100644 --- a/docs/type_annotations.md +++ b/docs/type_annotations.md @@ -41,7 +41,7 @@ In general string type hints must be used to provide Pyccel with information abo ## Tuples -Currently tuples are supported locally in Pyccel but cannot be passed as arguments or returned. The implementation of the type annotations (as a first step to adding the missing support) is in progress. Currently homogeneous tuple type annotations are supported for local variables. Internally we handle homogeneous tuples as thought they were NumPy arrays. When creating multiple dimensional tuples it is therefore important to ensure that all objects have compatible sizes otherwise they will be handled as inhomogeneous tuples. +Currently Pyccel supports tuples used locally in functions and in certain cases as arguments, but not as returned objects or module variables. The implementation of the type annotations (including adding the missing support) is in progress. Currently homogeneous tuple type annotations are supported for local variables and function arguments (if the tuples contain scalar obects). Internally we handle homogeneous tuples as thought they were NumPy arrays. When creating multiple dimensional tuples it is therefore important to ensure that all objects have compatible sizes otherwise they will be handled as inhomogeneous tuples. To declare a homogeneous tuple the syntax is as follows: ```python From 7ad524bd29eeb45df7e498b0a57faf5ebfb4c711 Mon Sep 17 00:00:00 2001 From: EmilyBourne Date: Thu, 2 May 2024 21:45:27 +0200 Subject: [PATCH 24/28] Correct symbol search --- pyccel/parser/semantic.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyccel/parser/semantic.py b/pyccel/parser/semantic.py index d83cf62a50..90b62a77c0 100644 --- a/pyccel/parser/semantic.py +++ b/pyccel/parser/semantic.py @@ -3772,8 +3772,7 @@ def unpack(ann): # Filter out unused templates templatable_args = [unpack(a.annotation) for a in expr.arguments if isinstance(a.annotation, (SyntacticTypeAnnotation, UnionTypeAnnotation))] arg_annotations = [annot for a in templatable_args for annot in a if isinstance(annot, SyntacticTypeAnnotation)] - type_names = [a.dtype for a in arg_annotations] - used_type_names = set(d for t in type_names for d in t.get_attribute_nodes(PyccelSymbol)) + used_type_names = set(t for a in arg_annotations for t in a.get_attribute_nodes(PyccelSymbol)) templates = {t: v for t,v in templates.items() if t in used_type_names} # Create new temparary templates for the arguments with a Union data type. From a9ca66e7706b2ae10f88907f6436738810d6bec2 Mon Sep 17 00:00:00 2001 From: EmilyBourne Date: Thu, 2 May 2024 21:56:22 +0200 Subject: [PATCH 25/28] Describe new _extract_X_FunctionDefArgument function and add an example --- developer_docs/wrapper_stage.md | 77 ++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 2 deletions(-) diff --git a/developer_docs/wrapper_stage.md b/developer_docs/wrapper_stage.md index 204e2aa7a2..82c8345fb5 100644 --- a/developer_docs/wrapper_stage.md +++ b/developer_docs/wrapper_stage.md @@ -376,7 +376,9 @@ PyObject* func_name(PyObject* self, PyObject* args, PyObject* kwargs); The arguments and keyword arguments are unpacked into individual `PyObject` pointers. Each of these objects is checked to verify the type. If the type does not match the expected type then an error is raised as described in the [C-API documentation](https://docs.python.org/3/c-api/intro.html#exceptions). -If the type does match then the value is unpacked into a C object. This is done using custom functions defined in `pyccel/stdlib/cwrapper/` or `pyccel/stdlib/cwrapper_ndarrays/` (see these files for more details). +If the type does match then the value is unpacked into a C object. This is done using custom functions defined in `pyccel/stdlib/cwrapper/` or `pyccel/stdlib/cwrapper_ndarrays/` (see these files for more details) or using functions provided by `Python.h`. + +In order to create all the nodes necessary to describe the unpacking of the arguments we use functions named `_extract_X_FunctionDefArgument` where `X` is the type of the object being extracted from the `FunctionDefArgument`. This allows such functions to call each other recursively. This is notably useful for container types (tuples, lists, etc) whose elements may themselves be container types or scalar types whose type must be checked using the same functions that would be necessary were the element itself an argument. Once C objects have been retrieved the function is called normally. @@ -384,7 +386,7 @@ Finally all the arguments are packed into a Python tuple stored in a `PyObject` The wrapper is attached to the module via a `PyMethodDef` (see C-API [docs](https://docs.python.org/3/c-api/structures.html#c.PyMethodDef)). -#### Example +#### Example 1 The following Python code: ```python @@ -543,6 +545,77 @@ PyObject* f_wrapper(PyObject* self, PyObject* args, PyObject* kwargs) } ``` +#### Example 2 : function with tuple arguments + +The following Python code: +```python +def my_tuple_int(a : 'tuple[int,...]'): + return a[0] +``` + +leads to C code with the following prototype (as homogeneous tuples are treated like arrays): +```c +int64_t my_tuple_int(t_ndarray a); +``` + +which is then wrapped as follows: +```c +static PyObject* my_tuple_int_wrapper(PyObject* self, PyObject* args, PyObject* kwargs) +{ + PyObject* a_obj; + t_ndarray a = {.shape = NULL}; + int Dummy_0000; + int64_t a_size; + PyObject* Dummy_0001; + bool is_homog_tuple; + int64_t size; + int Dummy_0002; + PyObject* Dummy_0003; + int64_t Out_0001; + PyObject* Out_0001_obj; + static char *kwlist[] = { + "a", + NULL + }; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O", kwlist, &a_obj)) + { + return NULL; + } + if (PyTuple_Check(a_obj)) + { + size = PyTuple_Size(a_obj); + is_homog_tuple = 1; + for (Dummy_0002 = INT64_C(0); Dummy_0002 < size; Dummy_0002 += INT64_C(1)) + { + Dummy_0003 = PyTuple_GetItem(a_obj, Dummy_0002); + is_homog_tuple = is_homog_tuple && PyIs_NativeInt(Dummy_0003); + } + } + else + { + is_homog_tuple = 0; + } + if (is_homog_tuple) + { + a_size = PyTuple_Size(a_obj); + a = array_create(1, (int64_t[]){a_size}, nd_int64, false, order_c); + for (Dummy_0000 = INT64_C(0); Dummy_0000 < a_size; Dummy_0000 += INT64_C(1)) + { + Dummy_0001 = PyTuple_GetItem(a_obj, Dummy_0000); + GET_ELEMENT(a, nd_int64, (int64_t)Dummy_0000) = PyInt64_to_Int64(Dummy_0001); + } + } + else + { + PyErr_SetString(PyExc_TypeError, "Expected an argument of type tuple[int, ...] for argument a"); + return NULL; + } + Out_0001 = my_tuple_int(a); + Out_0001_obj = Int64_to_PyLong(&Out_0001); + return Out_0001_obj; +} +``` + ### Interfaces Interfaces are functions which accept more than one type. From 5020a7413c5529fd5781f694669982a480529e35 Mon Sep 17 00:00:00 2001 From: EmilyBourne Date: Thu, 2 May 2024 22:07:55 +0200 Subject: [PATCH 26/28] Raise error for multi-level tuples in Fortran wrapping --- pyccel/ast/bind_c.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyccel/ast/bind_c.py b/pyccel/ast/bind_c.py index d5ca1d2d8b..a0b47f9af1 100644 --- a/pyccel/ast/bind_c.py +++ b/pyccel/ast/bind_c.py @@ -15,8 +15,11 @@ from pyccel.ast.datatypes import FixedSizeType, PythonNativeInt from pyccel.ast.numpytypes import NumpyNDArrayType from pyccel.ast.variable import Variable +from pyccel.errors.errors import Errors from pyccel.utilities.metaclasses import Singleton +errors = Errors() + __all__ = ( 'BindCArrayVariable', 'BindCClassDef', @@ -209,6 +212,9 @@ def __init__(self, var, scope, original_arg_var, wrapping_bound_argument, **kwar name=f'{name}_stride_{i+1}') for i in range(self._rank)] else: + if original_arg_var.rank > 1: + errors.report("Wrapping multi-level tuples is not yet supported", + severity='fatal', symbol=original_arg_var) strides = [] self._shape = shape self._strides = strides From 7f9c6de04ba2dea7381875232b7e5230ef0b7931 Mon Sep 17 00:00:00 2001 From: EmilyBourne Date: Thu, 2 May 2024 22:19:26 +0200 Subject: [PATCH 27/28] Typo --- docs/type_annotations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/type_annotations.md b/docs/type_annotations.md index 9e2015630d..2fb9adf68c 100644 --- a/docs/type_annotations.md +++ b/docs/type_annotations.md @@ -41,7 +41,7 @@ In general string type hints must be used to provide Pyccel with information abo ## Tuples -Currently Pyccel supports tuples used locally in functions and in certain cases as arguments, but not as returned objects or module variables. The implementation of the type annotations (including adding the missing support) is in progress. Currently homogeneous tuple type annotations are supported for local variables and function arguments (if the tuples contain scalar obects). Internally we handle homogeneous tuples as thought they were NumPy arrays. When creating multiple dimensional tuples it is therefore important to ensure that all objects have compatible sizes otherwise they will be handled as inhomogeneous tuples. +Currently Pyccel supports tuples used locally in functions and in certain cases as arguments, but not as returned objects or module variables. The implementation of the type annotations (including adding the missing support) is in progress. Currently homogeneous tuple type annotations are supported for local variables and function arguments (if the tuples contain scalar objects). Internally we handle homogeneous tuples as thought they were NumPy arrays. When creating multiple dimensional tuples it is therefore important to ensure that all objects have compatible sizes otherwise they will be handled as inhomogeneous tuples. To declare a homogeneous tuple the syntax is as follows: ```python From d74e74b4fe527a7cee2bab6e1206acc827db1de5 Mon Sep 17 00:00:00 2001 From: EmilyBourne Date: Thu, 2 May 2024 22:30:39 +0200 Subject: [PATCH 28/28] Test works for Python --- tests/epyccel/test_tuples.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/tests/epyccel/test_tuples.py b/tests/epyccel/test_tuples.py index 9491c93723..c9e8ecf400 100644 --- a/tests/epyccel/test_tuples.py +++ b/tests/epyccel/test_tuples.py @@ -173,12 +173,15 @@ def test_multi_level_tuple_arg(language): def my_tuple(a : 'tuple[tuple[int,...],...]'): return len(a), len(a[0]), a[0][0], a[1][0], a[0][1], a[1][1] - #tuple_arg = ((1,2), (3,4)) - - # Raises an error because tuples inside tuples may have different lengths - # This could be removed once lists are supported as the tuples could then - # be stored in lists instead of arrays. - with pytest.raises(PyccelError): - _ = epyccel(my_tuple, language=language) - - #assert my_tuple(tuple_arg) == epyc_func(tuple_arg) + tuple_arg = ((1,2), (3,4)) + + if language != 'python': + # Raises an error because tuples inside tuples may have different lengths + # This could be removed once lists are supported as the tuples could then + # be stored in lists instead of arrays. + with pytest.raises(PyccelError): + _ = epyccel(my_tuple, language=language) + else: + epyc_func = epyccel(my_tuple, language=language) + + assert my_tuple(tuple_arg) == epyc_func(tuple_arg)