Skip to content

Commit 1d735e8

Browse files
authored
Allow the use of a list comprehension to initialise an array (pyccel#2082)
Allow the use of a list comprehension to initialise an array. Fixes pyccel#2080 . The type of the target that is created from the `FunctionalFor` is saved into the `FunctionalFor`. This extra parameter will also be useful when support for set comprehension and/or dictionary comprehension is added.
1 parent 9327f8d commit 1d735e8

File tree

7 files changed

+79
-9
lines changed

7 files changed

+79
-9
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ All notable changes to this project will be documented in this file.
8686
- #2013 : Stop limiting the length of strings to 128 characters.
8787
- #2078 : Fix translation of classes containing comments.
8888
- #2041 : Include all type extension methods by default.
89+
- #2082 : Allow the use of a list comprehension to initialise an array.
8990

9091
### Changed
9192

ci_tools/check_pylint_commands.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
re.compile('tests/pyccel/project_class_imports/.*'):['relative-beyond-top-level'], # ignore Codacy bad pylint call
2424
re.compile('tests/errors/syntax_errors/import_star.py'):['wildcard-import'],
2525
re.compile('tests/stc_containers/leaks_check.py'):['unused-variable'],
26+
re.compile('tests/epyccel/modules/arrays.py'):['reimported'], # Repeat NumPy imports due to functions being translated individually
2627
}
2728

2829
def run_pylint(file, flag, messages):

pyccel/ast/functionalexpr.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,12 @@ class FunctionalFor(TypedAstNode):
5353
Dummy_0 += 1
5454
```
5555
Index is `Dummy_0`.
56+
target_type : PyccelSymbol, optional
57+
The type of the result of the functional for. This is useful at
58+
the syntactic stage to pass along the final type of the lhs (list/set/array/etc).
5659
"""
5760
__slots__ = ('_loops','_expr', '_lhs', '_indices','_index',
58-
'_shape','_class_type')
61+
'_shape','_class_type', '_target_type')
5962
_attribute_nodes = ('_loops','_expr', '_lhs', '_indices','_index')
6063

6164
def __init__(
@@ -64,13 +67,15 @@ def __init__(
6467
expr=None,
6568
lhs=None,
6669
indices=None,
67-
index=None
70+
index=None,
71+
target_type=None
6872
):
6973
self._loops = loops
7074
self._expr = expr
7175
self._lhs = lhs
7276
self._indices = indices
7377
self._index = index
78+
self._target_type = target_type
7479
super().__init__()
7580

7681
if pyccel_stage != 'syntactic':
@@ -97,6 +102,16 @@ def indices(self):
97102
def index(self):
98103
return self._index
99104

105+
@property
106+
def target_type(self):
107+
"""
108+
The type of the result of the functional for.
109+
110+
The type of the result of the functional for. This is useful at
111+
the syntactic stage to pass along the final type of the lhs (list/set/array/etc).
112+
"""
113+
return self._target_type
114+
100115
#==============================================================================
101116
class GeneratorComprehension(FunctionalFor):
102117
""" Super class for all functions which reduce generator expressions to scalars

pyccel/parser/semantic.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@
150150
PythonTupleFunction : HomogeneousTupleType,
151151
PythonListFunction : HomogeneousListType,
152152
PythonSetFunction : HomogeneousSetType,
153+
NumpyArray : NumpyNDArrayType,
153154
}
154155

155156
#==============================================================================
@@ -3796,7 +3797,7 @@ def _visit_FunctionalFor(self, expr):
37963797
dim = sympy_to_pyccel(dim, idx_subs)
37973798
except TypeError:
37983799
errors.report(PYCCEL_RESTRICTION_LIST_COMPREHENSION_SIZE + f'\n Deduced size : {dim}',
3799-
bounding_box=(self.current_ast_node.lineno, self.current_ast_node.col_offset),
3800+
symbol=expr,
38003801
severity='fatal')
38013802

38023803
# TODO find a faster way to calculate dim
@@ -3816,7 +3817,35 @@ def _visit_FunctionalFor(self, expr):
38163817
severity='fatal')
38173818

38183819
d_var['memory_handling'] = 'heap'
3819-
class_type = HomogeneousListType(class_type)
3820+
target_type_name = expr.target_type
3821+
if isinstance(target_type_name, DottedName):
3822+
lhs = target_type_name.name[0] if len(target_type_name.name) == 2 \
3823+
else DottedName(*target_type_name.name[:-1])
3824+
first = self._visit(lhs)
3825+
if isinstance(first, Module):
3826+
conversion_func = first[target_type_name.name[-1]]
3827+
else:
3828+
conversion_func = None
3829+
else:
3830+
conversion_func = self.scope.find(target_type_name, 'functions')
3831+
if conversion_func is None:
3832+
if target_type_name in builtin_functions_dict:
3833+
conversion_func = PyccelFunctionDef(target_type_name,
3834+
builtin_functions_dict[target_type_name])
3835+
if conversion_func is None:
3836+
errors.report("Unrecognised output type from functional for.\n"+PYCCEL_RESTRICTION_TODO,
3837+
symbol=expr,
3838+
severity='fatal')
3839+
3840+
try:
3841+
class_type = type_container[conversion_func.cls_name](class_type)
3842+
except TypeError:
3843+
if class_type.rank > 0:
3844+
errors.report("ND comprehension expressions cannot be saved directly to an array yet.\n"+PYCCEL_RESTRICTION_TODO,
3845+
symbol=expr,
3846+
severity='fatal')
3847+
3848+
class_type = type_container[conversion_func.cls_name](numpy_process_dtype(class_type), rank=1, order=None)
38203849
d_var['class_type'] = class_type
38213850
shape = [dim]
38223851
if d_var['shape']:

pyccel/parser/syntactic.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1099,7 +1099,7 @@ def _visit_Call(self, stmt):
10991099
if len(args) == 0:
11001100
args = ()
11011101

1102-
if len(args) == 1 and isinstance(args[0].value, GeneratorComprehension):
1102+
if len(args) == 1 and isinstance(args[0].value, (GeneratorComprehension, FunctionalFor)):
11031103
return args[0].value
11041104

11051105
func = self._visit(stmt.func)
@@ -1165,18 +1165,30 @@ def _visit_ListComp(self, stmt):
11651165

11661166
generators = list(self._visit(stmt.generators))
11671167

1168-
if not isinstance(self._context[-2],ast.Assign):
1168+
success = isinstance(self._context[-2],ast.Assign)
1169+
if not success and len(self._context) > 2:
1170+
success = isinstance(self._context[-3],ast.Assign) and isinstance(self._context[-2],ast.Call)
1171+
1172+
assignment = next((c for c in reversed(self._context) if isinstance(c, ast.Assign)), None)
1173+
1174+
if not success:
11691175
errors.report(PYCCEL_RESTRICTION_LIST_COMPREHENSION_ASSIGN,
11701176
symbol = stmt,
11711177
severity='error')
11721178
lhs = PyccelSymbol('_', is_temp=True)
11731179
else:
1174-
lhs = self._visit(self._context[-2].targets)
1180+
lhs = self._visit(assignment.targets)
11751181
if len(lhs)==1:
11761182
lhs = lhs[0]
11771183
else:
11781184
raise NotImplementedError("A list comprehension cannot be unpacked")
11791185

1186+
parent = self._context[-2]
1187+
if isinstance(parent, ast.Call):
1188+
output_type = self._visit(parent.func)
1189+
else:
1190+
output_type = 'list'
1191+
11801192
index = PyccelSymbol('_', is_temp=True)
11811193

11821194
args = [index]
@@ -1198,7 +1210,7 @@ def _visit_ListComp(self, stmt):
11981210
indices = indices[::-1]
11991211

12001212
return FunctionalFor([assign1, generators[-1]],target.rhs, target.lhs,
1201-
indices, index)
1213+
indices, index, target_type = output_type)
12021214

12031215
def _visit_GeneratorExp(self, stmt):
12041216

tests/epyccel/modules/arrays.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# pylint: disable=missing-function-docstring, missing-module-docstring
1+
# pylint: disable=missing-function-docstring, missing-module-docstring, reimported
22
import numpy as np
33

44
from pyccel.decorators import template, stack_array, allow_negative_index
@@ -71,6 +71,11 @@ def array_int_1d_initialization_3():
7171
b = np.array(a)
7272
return np.sum(b), b[0], b[-1]
7373

74+
def array_int_1d_initialization_4():
75+
import numpy as np
76+
b = np.array([i*2 for i in range(10)])
77+
return b
78+
7479
#==============================================================================
7580
# 2D ARRAYS OF INT-32 WITH C ORDERING
7681
#==============================================================================

tests/epyccel/test_arrays.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,13 @@ def test_array_int_1d_initialization_3(language):
346346

347347
assert f1() == f2()
348348

349+
def test_array_int_1d_initialization_4(language):
350+
351+
f1 = arrays.array_int_1d_initialization_4
352+
f2 = epyccel( f1 , language = language)
353+
354+
check_array_equal(f1(), f2())
355+
349356
#==============================================================================
350357
# TEST: 2D ARRAYS OF INT-32 WITH C ORDERING
351358
#==============================================================================

0 commit comments

Comments
 (0)