Skip to content

Commit b378f03

Browse files
authored
Allow the use of magic methods to describe container methods (pyccel#2016)
Allow the use of magic methods to describe container methods. This is tested with `set.union` which is called via the `|` operator.
1 parent 9c0b45e commit b378f03

File tree

11 files changed

+352
-88
lines changed

11 files changed

+352
-88
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ All notable changes to this project will be documented in this file.
1414
- #1693 : Add Python support for list method `remove()`.
1515
- #1750 : Add Python support for set method `remove()`.
1616
- #1743 : Add Python support for set method `discard()`.
17-
- #1754 : Add Python support for set method `update()`.
1817
- #1893 : Add Python support for set initialisation with `set()`.
1918
- #1895 : Add Python support for dict initialisation with `{}`.
2019
- #1895 : Add Python support for dict initialisation with `dict()`.
@@ -45,9 +44,11 @@ All notable changes to this project will be documented in this file.
4544
- #1583 : Allow inhomogeneous tuples in classes.
4645
- #738 : Add support for homogeneous tuples with scalar elements as arguments.
4746
- Add a warning about containers in lists.
47+
- #2016 : Add support for translating arithmetic magic methods (methods cannot yet be used from Python).
4848
- \[INTERNALS\] Add abstract class `SetMethod` to handle calls to various set methods.
4949
- \[INTERNALS\] Added `container_rank` property to `ast.datatypes.PyccelType` objects.
5050
- \[INTERNALS\] Add a `__call__` method to `FunctionDef` to create `FunctionCall` instances.
51+
- \[INTERNALS\] Allow the use of magic methods to describe container methods.
5152
- \[DEVELOPER\] Added an improved traceback to the developer-mode errors for errors in function calls.
5253

5354
### Fixed

developer_docs/semantic_stage.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ Instead they are recognised via the function [`pyccel.ast.utilities.builtin_func
112112
Functions from supported libraries are saved in an object of type [`pyccel.ast.core.PyccelFunctionDef`](../pyccel/ast/core.py) when they are imported.
113113
These functions are handled one of two ways.
114114
If there is special treatment which requires functions from the `SemanticParser` (e.g. handling inhomogeneous tuples or adding new imports) then a `_build_X` function should be created.
115-
Differently than the `_visit_X` functions, a `_build_X` function does not take an object of type `X` as argument, but rather a `FunctionCall` to the class `X`. In other words it does not visit `X`, but rather the call `X()`. If `X` represents a method of a class then `_build_X` takes a `DottedName` instead of a `FunctionCall`.
115+
Differently than the `_visit_X` functions, a `_build_X` function does not take an object of type `X` as argument, but rather a `FunctionCall` to the class `X` and the visited `FunctionCallArgument` objects. In other words it does not visit `X`, but rather the call `X()`. If `X` represents a method of a class then `_build_X` takes a `DottedName` instead of a `FunctionCall`.
116116
The `SemanticParser._visit_FunctionCall` function will call this visitation function internally if it exists.
117117
Otherwise the object will be created in the `SemanticParser._handle_function` function and its type will be determined by its constructor.
118118

pyccel/ast/class_defs.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@
166166
PyccelFunctionDef('remove', func_class = SetRemove),
167167
PyccelFunctionDef('union', func_class = SetUnion),
168168
PyccelFunctionDef('update', func_class = SetUpdate),
169+
PyccelFunctionDef('__or__', func_class = SetUnion),
170+
PyccelFunctionDef('__ior__', func_class = SetUpdate),
169171
])
170172

171173
#=======================================================================================

pyccel/ast/core.py

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
from pyccel.utilities.stage import PyccelStage
1313

1414
from .basic import PyccelAstNode, TypedAstNode, iterable, ScopedAstNode
15+
16+
from .bitwise_operators import PyccelBitOr, PyccelBitAnd
17+
1518
from .builtins import (PythonEnumerate, PythonLen, PythonMap, PythonTuple,
1619
PythonRange, PythonZip, PythonBool, Lambda)
1720

@@ -789,7 +792,10 @@ class AugAssign(Assign):
789792
'-' : PyccelMinus,
790793
'*' : PyccelMul,
791794
'/' : PyccelDiv,
792-
'%' : PyccelMod}
795+
'%' : PyccelMod,
796+
'|' : PyccelBitOr,
797+
'&' : PyccelBitAnd,
798+
}
793799

794800
def __init__(
795801
self,
@@ -812,8 +818,22 @@ def __repr__(self):
812818

813819
@property
814820
def op(self):
821+
"""
822+
Get the string describing the operator which modifies the lhs variable.
823+
824+
Get the string describing the operator which modifies the lhs variable.
825+
"""
815826
return self._op
816827

828+
@property
829+
def pyccel_operator(self):
830+
"""
831+
Get the PyccelOperator which modifies the lhs variable.
832+
833+
Get the PyccelOperator which modifies the lhs variable.
834+
"""
835+
return self._accepted_operators[self._op]
836+
817837
def to_basic_assign(self):
818838
"""
819839
Convert the AugAssign to an Assign.
@@ -3619,7 +3639,7 @@ def add_new_interface(self, interface):
36193639
interface.set_current_user_node(self)
36203640
self._interfaces += (interface,)
36213641

3622-
def get_method(self, name):
3642+
def get_method(self, name, raise_error = True):
36233643
"""
36243644
Get the method `name` of the current class.
36253645
@@ -3633,6 +3653,11 @@ def get_method(self, name):
36333653
name : str
36343654
The name of the attribute we are looking for.
36353655
3656+
raise_error : bool, default=True
3657+
True if an error should be raised, False if None should be returned if
3658+
the method is not found.
3659+
False if None can be returned instead.
3660+
36363661
Returns
36373662
-------
36383663
FunctionDef
@@ -3648,8 +3673,11 @@ def get_method(self, name):
36483673
try:
36493674
name = self.scope.get_expected_name(name)
36503675
except RuntimeError:
3651-
errors.report(f"Can't find method {name} in class {self.name}",
3652-
severity='fatal', symbol=self)
3676+
if raise_error:
3677+
errors.report(f"Can't find method {name} in class {self.name}",
3678+
severity='fatal', symbol=self)
3679+
else:
3680+
return None
36533681

36543682
try:
36553683
method = next(i for i in chain(self.methods, self.interfaces) if i.name == name)
@@ -3659,11 +3687,12 @@ def get_method(self, name):
36593687
n_classes = len(self.superclasses)
36603688
while method is None and i<n_classes:
36613689
try:
3662-
method = self.superclasses[i].get_method(name)
3690+
method = self.superclasses[i].get_method(name, raise_error)
36633691
except StopIteration:
36643692
method = None
3693+
i += 1
36653694

3666-
if method is None:
3695+
if method is None and raise_error:
36673696
errors.report(f"Can't find method {name} in class {self.name}",
36683697
severity='fatal', symbol=self)
36693698

pyccel/naming/cnameclashchecker.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,10 @@ def get_collisionless_name(self, name, symbols):
8181
str
8282
A new name which is collision free.
8383
"""
84-
if len(name)>4 and all(name[i] == '_' for i in (0,1,-1,-2)):
85-
# Ignore magic methods
84+
if name in ('__init__', '__del__'):
8685
return name
86+
if len(name)>4 and all(name[i] == '_' for i in (0,1,-1,-2)):
87+
name = 'operator' + name[1:-2]
8788
if name[0] == '_':
8889
name = 'private'+name
8990
return self._get_collisionless_name(name, symbols)

pyccel/naming/fortrannameclashchecker.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,10 @@ def get_collisionless_name(self, name, symbols):
9191
str
9292
A new name which is collision free.
9393
"""
94-
if len(name)>4 and all(name[i] == '_' for i in (0,1,-1,-2)):
95-
# Ignore magic methods
94+
if name in ('__init__', '__del__'):
9695
return name
96+
if len(name)>4 and all(name[i] == '_' for i in (0,1,-1,-2)):
97+
name = 'operator' + name[1:-2]
9798
if name[0] == '_':
9899
name = 'private'+name
99100
name = self._get_collisionless_name(name, symbols)

0 commit comments

Comments
 (0)