Skip to content

Commit d422cac

Browse files
Set support: update method (pyccel#1843)
Add support for the update method of Python setsto the semantic stage. Add handling of that method to the Python printer. This fixes pyccel#1754. **Commit Summary** * Add the `SetUpdate` class only to document the behavior of update() and its Pyccel implementation. * Create `_build_SetUpate`, which, depending on the argument passed to update(), tries to create semantic nodes of `add()` or a syntactic node of a `For` loop and then re-parse it. Add tests for multiple list methods involving many scenarios where the functions could be used. This PR builds upon the foundation established in pyccel#1731. --------- Co-authored-by: EmilyBourne <[email protected]>
1 parent ecf49b2 commit d422cac

File tree

8 files changed

+182
-12
lines changed

8 files changed

+182
-12
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ All notable changes to this project will be documented in this file.
1515
- #1740 : Add Python support for set method `copy()`.
1616
- #1750 : Add Python support for set method `remove()`.
1717
- #1743 : Add Python support for set method `discard()`.
18+
- #1754 : Add Python support for set method `update()`.
1819
- #1787 : Ensure `STC` is installed with Pyccel.
1920
- #1656 : Ensure `gFTL` is installed with Pyccel.
2021
- #1830 : Add a `pyccel.lambdify.lambdify` function to accelerate SymPy expressions.

pyccel/ast/builtin_methods/list_methods.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,12 +188,12 @@ class ListExtend(ListMethod):
188188
Represents a call to the .extend() method of an object with a list type,
189189
which adds items of an iterable (list, tuple, dictionary, etc) at the end
190190
of a list.
191-
This method is handled through the call to `_visit_ListExtend` in
191+
This method is handled through the call to `_build_ListExtend` in
192192
the semantic stage. It then attempts to construct a `For` loop node with
193193
a body that calls `append()`, or direct `append()` nodes depending on
194194
the type of the iterable passed to `extend()`.
195195
This class should never be instantiated; it's only purpose is to help
196-
construct the annotation_method `_visit_ListExtend`.
196+
construct the annotation_method `_build_ListExtend`.
197197
The extend method is called as follows:
198198
199199
>>> a = [1, 2, 3]

pyccel/ast/builtin_methods/set_methods.py

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
'SetDiscard',
2121
'SetMethod',
2222
'SetPop',
23-
'SetRemove'
23+
'SetRemove',
24+
'SetUpdate'
2425
)
2526

2627
class SetMethod(PyccelFunction):
@@ -53,7 +54,7 @@ def set_variable(self):
5354
"""
5455
return self._set_variable
5556

56-
57+
#==============================================================================
5758
class SetAdd(SetMethod) :
5859
"""
5960
Represents a call to the .add() method.
@@ -80,7 +81,7 @@ def __init__(self, set_variable, new_elem) -> None:
8081
raise TypeError("Expecting an argument of the same type as the elements of the set")
8182
super().__init__(set_variable, new_elem)
8283

83-
84+
#==============================================================================
8485
class SetClear(SetMethod):
8586
"""
8687
Represents a call to the .clear() method.
@@ -101,7 +102,7 @@ class SetClear(SetMethod):
101102
def __init__(self, set_variable):
102103
super().__init__(set_variable)
103104

104-
105+
#==============================================================================
105106
class SetCopy(SetMethod):
106107
"""
107108
Represents a call to the .copy() method.
@@ -122,7 +123,7 @@ def __init__(self, set_variable):
122123
self._class_type = set_variable._class_type
123124
super().__init__(set_variable)
124125

125-
126+
#==============================================================================
126127
class SetPop(SetMethod):
127128
"""
128129
Represents a call to the .pop() method.
@@ -146,7 +147,7 @@ def __init__(self, set_variable):
146147
self._class_type = set_variable.class_type.element_type
147148
super().__init__(set_variable)
148149

149-
150+
#==============================================================================
150151
class SetRemove(SetMethod):
151152
"""
152153
Represents a call to the .remove() method.
@@ -174,7 +175,7 @@ def __init__(self, set_variable, item) -> None:
174175
raise TypeError(f"Can't remove an element of type {item.dtype} from a set of {set_variable.dtype}")
175176
super().__init__(set_variable, item)
176177

177-
178+
#==============================================================================
178179
class SetDiscard(SetMethod):
179180
"""
180181
Represents a call to the .discard() method.
@@ -200,3 +201,32 @@ def __init__(self, set_variable, item) -> None:
200201
if set_variable.class_type.element_type != item.class_type:
201202
raise TypeError("Expecting an argument of the same type as the elements of the set")
202203
super().__init__(set_variable, item)
204+
205+
#==============================================================================
206+
class SetUpdate(SetMethod):
207+
"""
208+
Represents a call to the .update() method.
209+
210+
Represents a call to the .update() method of an object with a set type,
211+
Which adds items from another set (or any other iterable).
212+
This method is handled through the call to `_build_SetUpdate` in
213+
the semantic stage. It then attempts to construct a `For` loop node with
214+
a body that calls `add()`, or direct `add()` nodes depending on
215+
the type of the iterable passed to `update()`.
216+
This class should never be instantiated; it's only purpose is to help
217+
construct the annotation_method `_build_SetUpdate`.
218+
The update method is called as follows:
219+
220+
Parameters
221+
----------
222+
set_obj : TypedAstNode
223+
The set object which the method is called from.
224+
The argument passed to update() method.
225+
iterable : TypedAstNode
226+
The item to search for, and remove.
227+
"""
228+
__slots__ = ()
229+
name = 'update'
230+
231+
def __init__(self, set_obj, iterable) -> None:
232+
super().__init__(set_obj, iterable)

pyccel/ast/class_defs.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
This module contains all types which define a python class which is automatically recognised by pyccel
77
"""
88

9-
from pyccel.ast.builtin_methods.set_methods import SetAdd, SetClear, SetCopy, SetPop, SetRemove, SetDiscard
9+
from pyccel.ast.builtin_methods.set_methods import SetAdd, SetClear, SetCopy, SetPop, SetRemove, SetDiscard, SetUpdate
1010
from pyccel.ast.builtin_methods.list_methods import (ListAppend, ListInsert, ListPop,
1111
ListClear, ListExtend, ListRemove,
1212
ListCopy, ListSort)
@@ -161,6 +161,7 @@
161161
PyccelFunctionDef('discard', func_class = SetDiscard),
162162
PyccelFunctionDef('pop', func_class = SetPop),
163163
PyccelFunctionDef('remove', func_class = SetRemove),
164+
PyccelFunctionDef('update', func_class = SetUpdate),
164165
])
165166

166167
#=======================================================================================

pyccel/parser/semantic.py

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
PythonTuple, Lambda, PythonMap)
3030

3131
from pyccel.ast.builtin_methods.list_methods import ListMethod, ListAppend
32+
from pyccel.ast.builtin_methods.set_methods import SetMethod, SetAdd
3233

3334
from pyccel.ast.core import Comment, CommentBlock, Pass
3435
from pyccel.ast.core import If, IfSection
@@ -3016,7 +3017,8 @@ def _visit_Assign(self, expr):
30163017
symbol=expr, severity='error')
30173018

30183019
# Checking for the result of _visit_ListExtend
3019-
if isinstance(rhs, For) or (isinstance(rhs, CodeBlock) and isinstance(rhs.body[0], ListMethod)):
3020+
if isinstance(rhs, For) or (isinstance(rhs, CodeBlock) and
3021+
isinstance(rhs.body[0], (ListMethod, SetMethod))):
30203022
return rhs
30213023
if isinstance(rhs, ConstructorCall):
30223024
return rhs
@@ -4573,7 +4575,7 @@ def _build_ListExtend(self, expr):
45734575
"""
45744576
Method to navigate the syntactic DottedName node of an `extend()` call.
45754577
4576-
The purpose of this `_visit` method is to construct new nodes from a syntactic
4578+
The purpose of this `_build` method is to construct new nodes from a syntactic
45774579
DottedName node. It checks the type of the iterable passed to `extend()`.
45784580
If the iterable is an instance of `PythonList` or `PythonTuple`, it constructs
45794581
a CodeBlock node where its body consists of `ListAppend` objects with the
@@ -4799,3 +4801,50 @@ def _build_CmathPhase(self, func_call):
47994801
else:
48004802
self.insert_import('math', AsName(MathAtan2, 'atan2'))
48014803
return MathAtan2(PythonImag(var), PythonReal(var))
4804+
4805+
def _build_SetUpdate(self, expr):
4806+
"""
4807+
Method to navigate the syntactic DottedName node of an `update()` call.
4808+
4809+
The purpose of this `_build` method is to construct new nodes from a syntactic
4810+
DottedName node. It checks the type of the iterable passed to `update()`.
4811+
If the iterable is an instance of `PythonList`, `PythonSet` or `PythonTuple`, it constructs
4812+
a CodeBlock node where its body consists of `SetAdd` objects with the
4813+
elements of the iterable. If not, it attempts to construct a syntactic `For`
4814+
loop to iterate over the iterable object and added its elements to the set
4815+
object. Finally, it passes to a `_visit()` call for semantic parsing.
4816+
4817+
Parameters
4818+
----------
4819+
expr : DottedName
4820+
The syntactic DottedName node that represent the call to `.update()`.
4821+
4822+
Returns
4823+
-------
4824+
PyccelAstNode
4825+
CodeBlock or For containing SetAdd objects.
4826+
"""
4827+
iterable = expr.name[1].args[0].value
4828+
if isinstance(iterable, (PythonList, PythonSet, PythonTuple)):
4829+
list_variable = self._visit(expr.name[0])
4830+
added_list = self._visit(iterable)
4831+
try:
4832+
store = [SetAdd(list_variable, a) for a in added_list]
4833+
except TypeError as e:
4834+
msg = str(e)
4835+
errors.report(msg, symbol=expr, severity='fatal')
4836+
return CodeBlock(store)
4837+
else:
4838+
pyccel_stage.set_stage('syntactic')
4839+
for_target = self.scope.get_new_name()
4840+
arg = FunctionCallArgument(for_target)
4841+
func_call = FunctionCall('add', [arg])
4842+
dotted = DottedName(expr.name[0], func_call)
4843+
lhs = PyccelSymbol('_', is_temp=True)
4844+
assign = Assign(lhs, dotted)
4845+
assign.set_current_ast(expr.python_ast)
4846+
body = CodeBlock([assign])
4847+
for_obj = For(for_target, iterable, body)
4848+
pyccel_stage.set_stage('semantic')
4849+
return self._visit(for_obj)
4850+

tests/epyccel/test_epyccel_sets.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,3 +203,83 @@ def Discard_wrong_arg():
203203
pyccel_result = epyccel_remove()
204204
python_result = Discard_wrong_arg()
205205
assert python_result == pyccel_result
206+
207+
def test_update_basic(language):
208+
def update_basic():
209+
a = {1, 2, 3}
210+
b = {4, 5, 6}
211+
a.update(b)
212+
return a
213+
214+
epyccel_update = epyccel(update_basic, language=language)
215+
pyccel_result = epyccel_update()
216+
python_result = update_basic()
217+
assert python_result == pyccel_result
218+
219+
def test_update_multiple(language):
220+
def update_multiple():
221+
a = {1, 2, 3}
222+
a.update({4, 5})
223+
a.update({6, 7, 8, 9})
224+
a.update({10})
225+
return a
226+
227+
epyccel_update = epyccel(update_multiple, language=language)
228+
pyccel_result = epyccel_update()
229+
python_result = update_multiple()
230+
assert python_result == pyccel_result
231+
232+
233+
def test_update_boolean_tuple(language):
234+
def update_boolean_tuple():
235+
a = {True}
236+
b = (False, True, False)
237+
a.update(b)
238+
return a
239+
epyccel_update = epyccel(update_boolean_tuple, language=language)
240+
pyccel_result = epyccel_update()
241+
python_result = update_boolean_tuple()
242+
assert python_result == pyccel_result
243+
244+
245+
def test_update_complex_list(language):
246+
def update_complex_list():
247+
a = {1j, 2 + 3j, 0 + 0j}
248+
b = {4j, 5j, 1 + 6j}
249+
a.update(b)
250+
return a
251+
epyccel_update = epyccel(update_complex_list, language=language)
252+
pyccel_result = epyccel_update()
253+
python_result = update_complex_list()
254+
assert python_result == pyccel_result
255+
256+
def test_update_range(language):
257+
def update_range():
258+
a = {1, 2, 3}
259+
a.update(range(4, 9))
260+
return a
261+
epyccel_update = epyccel(update_range, language=language)
262+
pyccel_result = epyccel_update()
263+
python_result = update_range()
264+
assert python_result == pyccel_result
265+
266+
def test_update_set_as_arg(language):
267+
def update_set_as_arg():
268+
a = {1, 2, 3}
269+
a.update({4, 5, 6})
270+
return a
271+
272+
epyccel_update = epyccel(update_set_as_arg, language=language)
273+
pyccel_result = epyccel_update()
274+
python_result = update_set_as_arg()
275+
assert python_result == pyccel_result
276+
277+
def test_update_tuple_as_arg(language):
278+
def update_tuple_as_arg():
279+
a = {1, 2, 3}
280+
a.update((4, 5, 6))
281+
return a
282+
epyccel_update = epyccel(update_tuple_as_arg, language=language)
283+
pyccel_result = epyccel_update()
284+
python_result = update_tuple_as_arg()
285+
assert python_result == pyccel_result
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# pylint: disable=missing-function-docstring, missing-module-docstring
2+
3+
a = {1.4, 6.2, 8.1}
4+
a.update({3j})
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# pylint: disable=missing-function-docstring, missing-module-docstring
2+
3+
a = {2, 9, 5}
4+
b = {8.9, 6.}
5+
a.update(b)

0 commit comments

Comments
 (0)