Skip to content

Commit bbe2443

Browse files
authored
Support dict variable declarations in C (pyccel#1943)
Add C support for `dict` initialisation and freeing in C. Fixes pyccel#1942
1 parent 52f544d commit bbe2443

File tree

4 files changed

+55
-28
lines changed

4 files changed

+55
-28
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ All notable changes to this project will be documented in this file.
2626
- #1895 : Add Python support for dict initialisation with `{}`.
2727
- #1895 : Add Python support for dict initialisation with `dict()`.
2828
- #1886 : Add Python support for dict method `pop()`.
29+
- #1944 : Add the appropriate C language equivalent for declaring a Python `dict` container using the STC library.
2930
- #1936 : Add missing C output for inline decorator example in documentation
3031
- #1937 : Optimise `pyccel.ast.basic.PyccelAstNode.substitute` method.
3132
- \[INTERNALS\] Added `container_rank` property to `ast.datatypes.PyccelType` objects.

docs/builtin-functions.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Python contains a limited number of builtin functions defined [here](https://doc
1919
| `compile` | No |
2020
| **`complex`** | **Yes** |
2121
| `delattr` | No |
22-
| *`dict`* | Preliminary Python support |
22+
| *`dict`* | Preliminary Python and C **unordered** support |
2323
| `dir` | No |
2424
| `divmod` | No |
2525
| **`enumerate`** | as a loop iterable |
@@ -76,6 +76,8 @@ Python contains a limited number of builtin functions defined [here](https://doc
7676

7777
## Dictionary methods
7878

79+
:warning: The dictionary support provided by Pyccel only covers unordered dictionaries.
80+
7981
| Method | Supported |
8082
|----------|-----------|
8183
| `clear` | No |

pyccel/codegen/printing/ccode.py

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
from pyccel.ast.builtins import PythonRange, PythonComplex
1313
from pyccel.ast.builtins import PythonPrint, PythonType
14-
from pyccel.ast.builtins import PythonList, PythonTuple, PythonSet
14+
from pyccel.ast.builtins import PythonList, PythonTuple, PythonSet, PythonDict
1515

1616
from pyccel.ast.core import Declare, For, CodeBlock
1717
from pyccel.ast.core import FuncAddressDeclare, FunctionCall, FunctionCallArgument
@@ -29,7 +29,7 @@
2929
from pyccel.ast.datatypes import TupleType, FixedSizeNumericType
3030
from pyccel.ast.datatypes import CustomDataType, StringType, HomogeneousTupleType, HomogeneousListType, HomogeneousSetType
3131
from pyccel.ast.datatypes import PrimitiveBooleanType, PrimitiveIntegerType, PrimitiveFloatingPointType, PrimitiveComplexType
32-
from pyccel.ast.datatypes import HomogeneousContainerType
32+
from pyccel.ast.datatypes import HomogeneousContainerType, DictType
3333

3434
from pyccel.ast.internals import Slice, PrecomputedCode, PyccelArrayShapeElement
3535

@@ -678,8 +678,13 @@ def init_stc_container(self, expr, assignment_var):
678678
The generated C code for the container initialization.
679679
"""
680680

681-
dtype = self.get_c_type(assignment_var.lhs.class_type)
682-
keyraw = '{' + ', '.join(self._print(a) for a in expr.args) + '}'
681+
class_type = assignment_var.lhs.class_type
682+
dtype = self.get_c_type(class_type)
683+
if isinstance(expr, PythonDict):
684+
dict_item_strs = [(self._print(k), self._print(v)) for k,v in zip(expr.keys, expr.values)]
685+
keyraw = '{' + ', '.join(f'{{{k}, {v}}}' for k,v in dict_item_strs) + '}'
686+
else:
687+
keyraw = '{' + ', '.join(self._print(a) for a in expr.args) + '}'
683688
container_name = self._print(assignment_var.lhs)
684689
init = f'{container_name} = c_init({dtype}, {keyraw});\n'
685690
return init
@@ -1013,15 +1018,22 @@ def _print_Import(self, expr):
10131018
for t in expr.target:
10141019
dtype = t.object.class_type
10151020
container_type = t.target
1016-
container_key = self.get_c_type(dtype.element_type)
1021+
if isinstance(dtype, DictType):
1022+
container_key_key = self.get_c_type(dtype.key_type)
1023+
container_val_key = self.get_c_type(dtype.value_type)
1024+
container_key = f'{container_key_key}_{container_val_key}'
1025+
element_decl = f'#define i_key {container_key_key}\n#define i_val {container_val_key}\n'
1026+
else:
1027+
container_key = self.get_c_type(dtype.element_type)
1028+
element_decl = f'#define i_key {container_key}\n'
10171029
header_guard_prefix = import_header_guard_prefix.get(source, '')
10181030
header_guard = f'{header_guard_prefix}_{container_type.upper()}'
1019-
code += (f'#ifndef {header_guard}\n'
1020-
f'#define {header_guard}\n'
1021-
f'#define i_type {container_type}\n'
1022-
f'#define i_key {container_key}\n'
1023-
f'#include <{source}.h>\n'
1024-
f'#endif // {header_guard}\n\n')
1031+
code += ''.join((f'#ifndef {header_guard}\n',
1032+
f'#define {header_guard}\n',
1033+
f'#define i_type {container_type}\n',
1034+
element_decl,
1035+
f'#include <{source}.h>\n',
1036+
f'#endif // {header_guard}\n\n'))
10251037
return code
10261038
# Get with a default value is not used here as it is
10271039
# slower and on most occasions the import will not be in the
@@ -1245,6 +1257,13 @@ def get_c_type(self, dtype):
12451257
i_type = f'{container_type}_{element_type}'
12461258
self.add_import(Import(f'stc/{container_type}', AsName(VariableTypeAnnotation(dtype), i_type)))
12471259
return i_type
1260+
elif isinstance(dtype, DictType):
1261+
container_type = 'hmap'
1262+
key_type = self.get_c_type(dtype.key_type).replace(' ', '_')
1263+
val_type = self.get_c_type(dtype.value_type).replace(' ', '_')
1264+
i_type = f'{container_type}_{key_type}_{val_type}'
1265+
self.add_import(Import(f'stc/{container_type}', AsName(VariableTypeAnnotation(dtype), i_type)))
1266+
return i_type
12481267
else:
12491268
key = dtype
12501269

@@ -1320,7 +1339,7 @@ def get_declare_type(self, expr):
13201339
rank = expr.rank
13211340

13221341
if rank > 0:
1323-
if isinstance(expr.class_type, (HomogeneousSetType, HomogeneousListType)):
1342+
if isinstance(expr.class_type, (HomogeneousSetType, HomogeneousListType, DictType)):
13241343
dtype = self.get_c_type(expr.class_type)
13251344
return dtype
13261345
if isinstance(expr.class_type,(HomogeneousTupleType, NumpyNDArrayType)):
@@ -1610,7 +1629,7 @@ def _print_PyccelArrayShapeElement(self, expr):
16101629
def _print_Allocate(self, expr):
16111630
free_code = ''
16121631
variable = expr.variable
1613-
if isinstance(variable.class_type, (HomogeneousListType, HomogeneousSetType)):
1632+
if isinstance(variable.class_type, (HomogeneousListType, HomogeneousSetType, DictType)):
16141633
return ''
16151634
if variable.rank > 0:
16161635
#free the array if its already allocated and checking if its not null if the status is unknown
@@ -1646,7 +1665,7 @@ def _print_Allocate(self, expr):
16461665
raise NotImplementedError(f"Allocate not implemented for {variable}")
16471666

16481667
def _print_Deallocate(self, expr):
1649-
if isinstance(expr.variable.class_type, (HomogeneousListType, HomogeneousSetType)):
1668+
if isinstance(expr.variable.class_type, (HomogeneousListType, HomogeneousSetType, DictType)):
16501669
variable_address = self._print(ObjectAddress(expr.variable))
16511670
container_type = self.get_c_type(expr.variable.class_type)
16521671
return f'{container_type}_drop({variable_address});\n'
@@ -2196,7 +2215,7 @@ def _print_Assign(self, expr):
21962215
if isinstance(rhs, (NumpyFull)):
21972216
return prefix_code+self.arrayFill(expr)
21982217
lhs = self._print(expr.lhs)
2199-
if isinstance(rhs, (PythonList, PythonSet)):
2218+
if isinstance(rhs, (PythonList, PythonSet, PythonDict)):
22002219
return prefix_code+self.init_stc_container(rhs, expr)
22012220
rhs = self._print(expr.rhs)
22022221
return prefix_code+'{} = {};\n'.format(lhs, rhs)

tests/epyccel/test_epyccel_variable_annotations.py

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import pytest
88

99
from pyccel import epyccel
10-
from pyccel.errors.errors import PyccelSemanticError, Errors
10+
from pyccel.errors.errors import PyccelSemanticError
1111
from pyccel.decorators import allow_negative_index, stack_array
1212

1313
@pytest.fixture( params=[
@@ -272,17 +272,22 @@ def homogeneous_list_annotation():
272272
assert epyc_homogeneous_list_annotation() == homogeneous_list_annotation()
273273
assert isinstance(epyc_homogeneous_list_annotation(), type(homogeneous_list_annotation()))
274274

275-
@pytest.mark.parametrize('lang',
276-
[pytest.param("python", marks = pytest.mark.python)])
277-
def test_dict_empty_init(lang):
278-
def dict_empty_init():
275+
def test_dict_int_float(stc_language):
276+
def dict_int_float():
279277
# Not valid in Python 3.8
280-
a : dict[int, float] #pylint: disable=unsubscriptable-object
278+
a : dict[int, float] #pylint: disable=unsubscriptable-object,unused-variable
281279
a = {1:1.0, 2:2.0}
282-
return a
283280

284-
epyc_dict_empty_init = epyccel(dict_empty_init, language = lang)
285-
pyccel_result = epyc_dict_empty_init()
286-
python_result = dict_empty_init()
287-
assert isinstance(python_result, type(pyccel_result))
288-
assert python_result == pyccel_result
281+
epyc_dict_int_float = epyccel(dict_int_float, language = stc_language)
282+
epyc_dict_int_float()
283+
dict_int_float()
284+
285+
def test_dict_empty_init(stc_language):
286+
def dict_empty_init():
287+
# Not valid in Python 3.8
288+
a : dict[int, float] #pylint: disable=unsubscriptable-object,unused-variable
289+
a = {}
290+
291+
epyc_dict_empty_init = epyccel(dict_empty_init, language = stc_language)
292+
epyc_dict_empty_init()
293+
dict_empty_init()

0 commit comments

Comments
 (0)