Skip to content

Commit 7024366

Browse files
authored
Allow returning sets and lists from Fortran (pyccel#2102)
Add support to the Fortran to C wrapper to allow sets and lists to be returned from Fortran. This is done by packing the elements into an array. Fixes pyccel#1667 . Fixes pyccel#1666
1 parent 4fb4fa5 commit 7024366

File tree

6 files changed

+55
-25
lines changed

6 files changed

+55
-25
lines changed

CHANGELOG.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ All notable changes to this project will be documented in this file.
3333
- #1874 : Add C and Fortran support for the `len()` function for the `list` container.
3434
- #1875 : Add C and Fortran support for the `len()` function for the `set` container.
3535
- #1908 : Add C and Fortran support for the `len()` function for the `dict` container.
36-
- #1665 : Add C support for returning lists from functions.
36+
- #1665 : Add C and Fortran support for returning lists from functions.
3737
- #1689 : Add C and Fortran support for list method `append()`.
3838
- #1876 : Add C support for indexing lists.
3939
- #1690 : Add C support for list method `pop()`.
40-
- #1663 : Add C support for sets as constant arguments.
41-
- #1664 : Add C support for returning sets from functions.
40+
- #1663 : Add C support for sets as arguments.
41+
- #1664 : Add C and Fortran support for returning sets from functions.
4242
- #2023 : Add support for iterating over a `set`.
4343
- #1877 : Add C and Fortran Support for set method `pop()`.
4444
- #1917 : Add C and Fortran support for set method `add()`.

pyccel/codegen/printing/ccode.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1813,7 +1813,7 @@ def _print_Deallocate(self, expr):
18131813
if isinstance(expr.variable.dtype, CustomDataType):
18141814
Pyccel__del = expr.variable.cls_base.scope.find('__del__').name
18151815
return f"{Pyccel__del}({variable_address});\n"
1816-
elif isinstance(expr.variable.class_type, (NumpyNDArrayType, HomogeneousContainerType)):
1816+
elif isinstance(expr.variable.class_type, (NumpyNDArrayType, HomogeneousTupleType)):
18171817
if expr.variable.is_alias:
18181818
return f'free_pointer({variable_address});\n'
18191819
else:

pyccel/codegen/wrapper/c_to_python_wrapper.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2758,13 +2758,17 @@ def _extract_HomogeneousContainerType_FunctionDefResult(self, orig_var, is_bind_
27582758
if isinstance(orig_var, PythonTuple):
27592759
return self._extract_InhomogeneousTupleType_FunctionDefResult(orig_var, is_bind_c, funcdef)
27602760

2761-
if is_bind_c:
2762-
raise NotImplementedError("Support for returning sets from Fortran code is not yet available")
2763-
27642761
name = getattr(orig_var, 'name', 'tmp')
27652762
py_res = self.get_new_PyObject(f'{name}_obj', orig_var.dtype)
2766-
c_res = orig_var.clone(self.scope.get_new_name(name), is_argument = False)
2767-
loop_size = Variable(PythonNativeInt(), self.scope.get_new_name(f'{name}_size'))
2763+
if is_bind_c:
2764+
c_res = Variable(CStackArray(orig_var.class_type.element_type),
2765+
self.scope.get_new_name(funcdef.results[0].var.name))
2766+
loop_size = funcdef.results[1].var.clone(self.scope.get_new_name(funcdef.results[1].var.name), is_argument = False)
2767+
c_results = [ObjectAddress(c_res), loop_size]
2768+
else:
2769+
c_res = orig_var.clone(self.scope.get_new_name(name), is_argument = False)
2770+
c_results = [c_res]
2771+
loop_size = Variable(PythonNativeInt(), self.scope.get_new_name(f'{name}_size'))
27682772
idx = Variable(PythonNativeInt(), self.scope.get_new_name())
27692773
self.scope.insert_variable(c_res)
27702774
self.scope.insert_variable(loop_size)
@@ -2777,7 +2781,10 @@ def _extract_HomogeneousContainerType_FunctionDefResult(self, orig_var, is_bind_
27772781

27782782
class_type = orig_var.class_type
27792783
if isinstance(class_type, HomogeneousSetType):
2780-
element = SetPop(c_res)
2784+
if is_bind_c:
2785+
element = IndexedElement(c_res, idx)
2786+
else:
2787+
element = SetPop(c_res)
27812788
elem_set = PySet_Add(py_res, element_extraction['py_result'])
27822789
init = PySet_New()
27832790
elif isinstance(class_type, HomogeneousListType):
@@ -2795,8 +2802,10 @@ def _extract_HomogeneousContainerType_FunctionDefResult(self, orig_var, is_bind_
27952802
*element_extraction['body'],
27962803
If(IfSection(PyccelEq(elem_set, PyccelUnarySub(LiteralInteger(1))),
27972804
[Return([self._error_exit_code])]))]
2798-
body = [Assign(loop_size, PythonLen(c_res)),
2799-
AliasAssign(py_res, init),
2800-
For((idx,), PythonRange(loop_size), for_body, for_scope)]
2805+
body = [Assign(loop_size, PythonLen(c_res))] if not is_bind_c else []
2806+
body += [AliasAssign(py_res, init),
2807+
For((idx,), PythonRange(loop_size), for_body, for_scope)]
2808+
if is_bind_c:
2809+
body.append(Deallocate(c_res))
28012810

2802-
return {'c_results': [c_res], 'py_result': py_res, 'body': body}
2811+
return {'c_results': c_results, 'py_result': py_res, 'body': body}

pyccel/codegen/wrapper/fortran_to_c_wrapper.py

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,20 @@
1313
from pyccel.ast.bind_c import CLocFunc, BindCModule, BindCVariable
1414
from pyccel.ast.bind_c import BindCArrayVariable, BindCClassDef, DeallocatePointer
1515
from pyccel.ast.bind_c import BindCClassProperty
16+
from pyccel.ast.builtins import VariableIterator
1617
from pyccel.ast.core import Assign, FunctionCall, FunctionCallArgument
1718
from pyccel.ast.core import Allocate, EmptyNode, FunctionAddress
1819
from pyccel.ast.core import If, IfSection, Import, Interface, FunctionDefArgument
1920
from pyccel.ast.core import AsName, Module, AliasAssign, FunctionDefResult
21+
from pyccel.ast.core import For
2022
from pyccel.ast.datatypes import CustomDataType, FixedSizeNumericType
2123
from pyccel.ast.datatypes import HomogeneousTupleType, TupleType
24+
from pyccel.ast.datatypes import HomogeneousSetType, PythonNativeInt
25+
from pyccel.ast.datatypes import HomogeneousListType
2226
from pyccel.ast.internals import Slice
2327
from pyccel.ast.literals import LiteralInteger, Nil, LiteralTrue
2428
from pyccel.ast.numpytypes import NumpyNDArrayType
25-
from pyccel.ast.operators import PyccelIsNot, PyccelMul
29+
from pyccel.ast.operators import PyccelIsNot, PyccelMul, PyccelAdd
2630
from pyccel.ast.variable import Variable, IndexedElement, DottedVariable
2731
from pyccel.ast.numpyext import NumpyNDArrayType
2832
from pyccel.errors.errors import Errors
@@ -419,14 +423,33 @@ def _wrap_FunctionDefResult(self, expr):
419423

420424
if not (var.is_alias or wrap_dotted):
421425
# Create an array variable which can be passed to CLocFunc
422-
ptr_var = var.clone(scope.get_new_name(name+'_ptr'),
426+
ptr_var = Variable(NumpyNDArrayType(var.dtype, var.rank, var.order), scope.get_new_name(name+'_ptr'),
423427
memory_handling='alias')
424428
scope.insert_variable(ptr_var)
425429

426430
# Define the additional steps necessary to define and fill ptr_var
427431
alloc = Allocate(ptr_var, shape=result.shape, status='unallocated')
428-
copy = Assign(ptr_var, local_var)
429-
self._additional_exprs.extend([alloc, copy])
432+
if isinstance(local_var.class_type, (NumpyNDArrayType, HomogeneousTupleType, CustomDataType)):
433+
copy = Assign(ptr_var, local_var)
434+
self._additional_exprs.extend([alloc, copy])
435+
elif isinstance(local_var.class_type, (HomogeneousSetType, HomogeneousListType)):
436+
iterator = VariableIterator(local_var)
437+
elem = Variable(var.class_type.element_type, self.scope.get_new_name())
438+
idx = Variable(PythonNativeInt(), self.scope.get_new_name())
439+
self.scope.insert_variable(elem)
440+
assign = Assign(idx, LiteralInteger(0))
441+
for_scope = self.scope.create_new_loop_scope()
442+
for_body = [Assign(IndexedElement(ptr_var, idx), elem)]
443+
if isinstance(local_var.class_type, HomogeneousSetType):
444+
self.scope.insert_variable(idx)
445+
for_body.append(Assign(idx, PyccelAdd(idx, LiteralInteger(1))))
446+
else:
447+
iterator.set_loop_counter(idx)
448+
fill_for = For((elem,), iterator, for_body, scope = for_scope)
449+
self._additional_exprs.extend([alloc, assign, fill_for])
450+
else:
451+
raise errors.report(f"Don't know how to return an object of type {local_var.class_type} to C code.",
452+
severity='fatal', symbol = var)
430453
else:
431454
ptr_var = var
432455

tests/epyccel/test_epyccel_lists.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -770,12 +770,12 @@ def list_ptr():
770770
assert isinstance(python_result, type(pyccel_result))
771771
assert python_result == pyccel_result
772772

773-
def test_list_return(stc_language):
773+
def test_list_return(language):
774774
def list_return():
775775
a = [1,2,3,4,5]
776776
return a
777777

778-
epyccel_func = epyccel(list_return, language = stc_language)
778+
epyccel_func = epyccel(list_return, language = language)
779779
pyccel_result = epyccel_func()
780780
python_result = list_return()
781781
assert python_result == pyccel_result

tests/epyccel/test_epyccel_sets.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -698,14 +698,12 @@ def set_arg(arg : 'set[int]', n : int):
698698
set_arg(arg_pyt, n)
699699
assert arg_pyc == arg_pyt
700700

701-
def test_set_return(stc_language):
701+
def test_set_return(language):
702702
def set_return():
703703
a = {1,2,3,4,5}
704-
b = {4,5,6}
705-
c = a.union(b) # Use union to avoid #2084
706-
return c
704+
return a
707705

708-
epyccel_func = epyccel(set_return, language = stc_language)
706+
epyccel_func = epyccel(set_return, language = language)
709707
pyccel_result = epyccel_func()
710708
python_result = set_return()
711709
assert python_result == pyccel_result

0 commit comments

Comments
 (0)