Repository: pyccel/pyccel (python)

Issue:
intersection : Returns a new set with elements that are common to all sets.
### Feature Description

**Add support for intersection() method for sets in the Python translation.**
`intersection(*others)` : Returns a new set containing elements that are present in all sets, the intersection of all sets.

### Test Code

This Code Should Work 

```python
set1 = {1, 2, 3}
set2 = {2, 3, 4}
intersect_set = set1.intersection(set2)
print(intersect_set) 
>>> {2, 3}
```

### Proposed Solution
See description in #1531 

Pull request:
Add support for set method intersection #1744
Add support for set method intersection. Fixes #1744

**Commit Summary**

- Add a class to represent `SetIntersection
-  Add intersection method to class description
-  add tests for set intersection method
-  update changelog and docs

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ca6291ca..bcf2cb4f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -44,6 +44,7 @@ All notable changes to this project will be documented in this file.
 -   #1918 : Add support for set method `clear()`.
 -   #1918 : Add support for set method `copy()`.
 -   #1753 : Add support for set method `union()`.
+-   #1744 : Add Python support for set method `intersection()`.
 -   #1884 : Add support for dict method `items()`.
 -   #1936 : Add missing C output for inline decorator example in documentation
 -   #1937 : Optimise `pyccel.ast.basic.PyccelAstNode.substitute` method.
diff --git a/docs/builtin-functions.md b/docs/builtin-functions.md
index cc613789..b7ca91c6 100644
--- a/docs/builtin-functions.md
+++ b/docs/builtin-functions.md
@@ -102,7 +102,7 @@ Python contains a limited number of builtin functions defined [here](https://doc
 | `difference` | No |
 | `difference_update` | No |
 | `discard` | Python-only |
-| `intersection` | No |
+| `intersection` | Python-only |
 | `intersection_update` | No |
 | `isdisjoint` | No |
 | `issubset` | No |
diff --git a/pyccel/ast/builtin_methods/set_methods.py b/pyccel/ast/builtin_methods/set_methods.py
index 782fee7d..85630e80 100644
--- a/pyccel/ast/builtin_methods/set_methods.py
+++ b/pyccel/ast/builtin_methods/set_methods.py
@@ -18,6 +18,7 @@ __all__ = (
     'SetClear',
     'SetCopy',
     'SetDiscard',
+    'SetIntersection',
     'SetMethod',
     'SetPop',
     'SetUnion',
@@ -232,3 +233,32 @@ class SetUnion(SetMethod):
                 raise TypeError(f"Argument of type {o.class_type} cannot be used to build set of type {self._class_type}")
         self._shape = (None,)*self._class_type.rank
         super().__init__(set_obj, *others)
+
+#==============================================================================
+
+class SetIntersection(SetMethod):
+    """
+    Represents a call to the set method .intersection.
+
+    Represents a call to the set method .intersection. This method builds a new set
+    by including all elements which appear in "both" of the iterables
+    (the set object and the arguments).
+
+    Parameters
+    ----------
+    set_obj : TypedAstNode
+        The set object which the method is called from.
+    *others : TypedAstNode
+        The iterables which will be combined (common elements) with this set.
+    """
+    __slots__ = ('_other','_class_type', '_shape')
+    name = 'intersection'
+
+    def __init__(self, set_obj, *others):
+        self._class_type = set_obj.class_type
+        element_type = self._class_type.element_type
+        for o in others:
+            if element_type != o.class_type.element_type:
+                raise TypeError(f"Argument fo type {o.type_class} cannot be used to build set of type {self._class_type}")
+        self._shape = (None,)*self.rank
+        super().__init__(set_obj, *others)
diff --git a/pyccel/ast/class_defs.py b/pyccel/ast/class_defs.py
index da4bee0b..9ecdc16d 100644
--- a/pyccel/ast/class_defs.py
+++ b/pyccel/ast/class_defs.py
@@ -7,7 +7,7 @@ This module contains all types which define a python class which is automaticall
 """
 
 from pyccel.ast.builtin_methods.set_methods  import (SetAdd, SetClear, SetCopy, SetPop,
-                                                     SetDiscard, SetUpdate, SetUnion)
+                                                     SetDiscard, SetUpdate, SetUnion, SetIntersection)
 from pyccel.ast.builtin_methods.list_methods import (ListAppend, ListInsert, ListPop,
                                                      ListClear, ListExtend, ListRemove,
                                                      ListCopy, ListSort)
@@ -166,8 +166,10 @@ SetClass = ClassDef('set',
             PyccelFunctionDef('pop', func_class = SetPop),
             PyccelFunctionDef('remove', func_class = SetDiscard),
             PyccelFunctionDef('union', func_class = SetUnion),
+            PyccelFunctionDef('intersection', func_class = SetIntersection),
             PyccelFunctionDef('update', func_class = SetUpdate),
             PyccelFunctionDef('__or__', func_class = SetUnion),
+            PyccelFunctionDef('__and__', func_class = SetIntersection),
             PyccelFunctionDef('__ior__', func_class = SetUpdate),
         ])
 
diff --git a/pyccel/parser/syntactic.py b/pyccel/parser/syntactic.py
index 8e32cd9f..8546eb35 100644
--- a/pyccel/parser/syntactic.py
+++ b/pyccel/parser/syntactic.py
@@ -442,6 +442,8 @@ class SyntaxParser(BasicParser):
             return AugAssign(lhs, '%', rhs)
         elif isinstance(stmt.op, ast.BitOr):
             return AugAssign(lhs, '|', rhs)
+        elif isinstance(stmt.op, ast.BitAnd):
+            return AugAssign(lhs, '&', rhs)
         else:
             return errors.report(PYCCEL_RESTRICTION_TODO, symbol = stmt,
                     severity='error')


Test patch:
diff --git a/tests/epyccel/test_epyccel_sets.py b/tests/epyccel/test_epyccel_sets.py
index e54f45ff..acb045b2 100644
--- a/tests/epyccel/test_epyccel_sets.py
+++ b/tests/epyccel/test_epyccel_sets.py
@@ -425,6 +425,46 @@ def test_set_union_2_args(language):
     assert python_result[0] == pyccel_result[0]
     assert set(python_result[1:]) == set(pyccel_result[1:])
 
+def test_set_intersection_int(python_only_language):
+    def intersection_int():
+        a = {1,2,3}
+        b = {2,3,4}
+        c = a.intersection(b)
+        return len(c), c.pop(), c.pop()
+
+    epyccel_func = epyccel(intersection_int, language = python_only_language)
+    pyccel_result = epyccel_func()
+    python_result = intersection_int()
+    assert python_result[0] == pyccel_result[0]
+    assert set(python_result[1:]) == set(pyccel_result[1:])
+
+def test_set_intersection_no_args(python_only_language):
+    def intersection_int():
+        a = {1,2,3,4}
+        c = a.intersection()
+        a.add(5)
+        return len(c), c.pop(), c.pop(), c.pop(), c.pop()
+
+    epyccel_func = epyccel(intersection_int, language = python_only_language)
+    pyccel_result = epyccel_func()
+    python_result = intersection_int()
+    assert python_result[0] == pyccel_result[0]
+    assert set(python_result[1:]) == set(pyccel_result[1:])
+
+def test_set_intersection_2_args(python_only_language):
+    def intersection_int():
+        a = {1,2,3,4}
+        b = {5,6,7,2,1,3}
+        c = {7,6,10,4,2,3,1}
+        d = a.intersection(b, c)
+        return len(d), d.pop(), d.pop(), d.pop()
+
+    epyccel_func = epyccel(intersection_int, language = python_only_language)
+    pyccel_result = epyccel_func()
+    python_result = intersection_int()
+    assert python_result[0] == pyccel_result[0]
+    assert set(python_result[1:]) == set(pyccel_result[1:])
+
 @pytest.mark.parametrize( 'language', (
         pytest.param("fortran", marks = pytest.mark.fortran),
         pytest.param("c", marks = [
@@ -485,6 +525,57 @@ def test_set_union_operator(language):
     assert python_result[0] == pyccel_result[0]
     assert set(python_result[1:]) == set(pyccel_result[1:])
 
+def test_temporary_set_intersection(python_only_language):
+    def intersection_int():
+        a = {1,2}
+        b = {2}
+        d = a.intersection(b).pop()
+        return d
+
+    epyccel_func = epyccel(intersection_int, language = python_only_language)
+    pyccel_result = epyccel_func()
+    python_result = intersection_int()
+    assert python_result == pyccel_result
+
+def test_set_intersection_list(python_only_language):
+    def intersection_list():
+        a = {1.2, 2.3, 5.0}
+        b = [1.2, 5.0, 4.0]
+        d = a.intersection(b)
+        return len(d), d.pop(), d.pop()
+
+    epyccel_func = epyccel(intersection_list, language = python_only_language)
+    pyccel_result = epyccel_func()
+    python_result = intersection_list()
+    assert python_result[0] == pyccel_result[0]
+    assert set(python_result[1:]) == set(pyccel_result[1:])
+
+def test_set_intersection_tuple(python_only_language):
+    def intersection_tuple():
+        a = {True}
+        b = (False, True)
+        d = a.intersection(b)
+        return len(d), d.pop()
+
+    epyccel_func = epyccel(intersection_tuple, language = python_only_language)
+    pyccel_result = epyccel_func()
+    python_result = intersection_tuple()
+    assert python_result[0] == pyccel_result[0]
+    assert set(python_result[1:]) == set(pyccel_result[1:])
+
+def test_set_intersection_operator(python_only_language):
+    def intersection_int():
+        a = {1,2,3,4,8}
+        b = {5,2,3,7,8}
+        c = a & b
+        return len(c), c.pop(), c.pop(), c.pop()
+
+    epyccel_func = epyccel(intersection_int, language = python_only_language)
+    pyccel_result = epyccel_func()
+    python_result = intersection_int()
+    assert python_result[0] == pyccel_result[0]
+    assert set(python_result[1:]) == set(pyccel_result[1:])
+
 @pytest.mark.parametrize( 'language', (
         pytest.param("fortran", marks = [
             pytest.mark.xfail(reason="Update not fully implemented yet. See #2022"),
@@ -521,6 +612,19 @@ def test_set_contains(language):
     python_result = union_int()
     assert python_result == pyccel_result
 
+def test_set_intersection_augoperator(python_only_language):
+    def intersection_int():
+        a = {1,2,3,4}
+        b = {2,3,4}
+        a &= b
+        return len(a), a.pop(), a.pop(), a.pop()
+
+    epyccel_func = epyccel(intersection_int, language = python_only_language)
+    pyccel_result = epyccel_func()
+    python_result = intersection_int()
+    assert python_result[0] == pyccel_result[0]
+    assert set(python_result[1:]) == set(pyccel_result[1:])
+
 def test_set_ptr(language):
     def set_ptr():
         a = {1,2,3,4,5,6,7,8}