Repository: pyccel/pyccel (python)

Issue:
Can't compare strings
### Describe the bug
Strings cannot be compared as the comparison triggers non-implemented `ContainerType` methods.

### To Reproduce
Provide code to reproduce the behaviour:
```python
ef f():
    option = 'hello'
    if option == 'do this':
        return 1.0
    else:
        return 2.0
```

### Error details
Provide the generated code, or the error message:
```bash/fortran/c
AttributeError: 'PrimitiveCharacterType' object has no attribute 'switch_basic_type'
```

### Expected behaviour
String comparison should work like integer comparison or floating point comparison.

### Language
Semantic error implies all languages.

### Proposed Solution
Stop `StringType` inheriting from `ContainerType`. This inheritance is not helpful and leads to many exceptions.

### Additional context
Related to #2194


Pull request description:
Fix string comparison
Fix string comparisons. Recurrent problems due to `StringType` inheritance are fixed by removing the inheritance of `ContainerType`. `CStrStr` is used with `strcmp` to implement comparison of strings in C. Fixes #2195.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cde14b19..0da42919 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -115,6 +115,7 @@ All notable changes to this project will be documented in this file.
 -   #2181 : Allow saving an array result of a function to a slice but raise a warning about suboptimal performance.
 -   #2190 : Fix missing error for list pointer assignment.
 -   #2198 : Fix saving an empty string in Fortran.
+-   #2195 : Fix string comparisons.
 -   Fixed returning strings from functions.
 -   Lifted the restriction on ndarrays limiting them to rank<15.
 -   #2175 : Fix the shape of multi-level containers.
diff --git a/pyccel/ast/datatypes.py b/pyccel/ast/datatypes.py
index 37f7f801..0a5f2f92 100644
--- a/pyccel/ast/datatypes.py
+++ b/pyccel/ast/datatypes.py
@@ -671,7 +671,7 @@ class HomogeneousContainerType(ContainerType):
     def __hash__(self):
         return hash((self.__class__, self.element_type))
 
-class StringType(HomogeneousContainerType, metaclass = Singleton):
+class StringType(ContainerType, metaclass = Singleton):
     """
     Class representing Python's native string type.
 
@@ -679,9 +679,6 @@ class StringType(HomogeneousContainerType, metaclass = Singleton):
     """
     __slots__ = ()
     _name = 'str'
-    _element_type = CharType()
-    _container_rank = 1
-    _order = None
 
     @property
     def datatype(self):
@@ -714,6 +711,36 @@ class StringType(HomogeneousContainerType, metaclass = Singleton):
         """
         return 1
 
+    @property
+    def container_rank(self):
+        """
+        Number of dimensions of the container.
+
+        Number of dimensions of the object described by the container. This is
+        equal to the number of values required to index an element of this container.
+        """
+        return 1
+
+    @property
+    def order(self):
+        """
+        The data layout ordering in memory.
+
+        Indicates whether the data is stored in row-major ('C') or column-major
+        ('F') format. This is only relevant if rank > 1. When it is not relevant
+        this function returns None.
+        """
+        return None
+
+    @property
+    def element_type(self):
+        """
+        The type of elements of the object.
+
+        The PyccelType describing an element of the container.
+        """
+        return CharType()
+
     def __eq__(self, other):
         return isinstance(other, self.__class__)
 
diff --git a/pyccel/ast/operators.py b/pyccel/ast/operators.py
index 9d406943..aebff713 100644
--- a/pyccel/ast/operators.py
+++ b/pyccel/ast/operators.py
@@ -23,6 +23,8 @@ from .literals              import Literal, LiteralInteger, LiteralFloat, Litera
 from .literals              import Nil, NilArgument, LiteralTrue, LiteralFalse
 from .literals              import convert_to_literal
 
+from .numpytypes            import NumpyNDArrayType
+
 errors = Errors()
 pyccel_stage = PyccelStage()
 
@@ -935,7 +937,7 @@ class PyccelComparisonOperator(PyccelBinaryOperator):
         """
         dtype = PythonNativeBool()
         possible_class_types = set(a.class_type for a in (arg1, arg2) \
-                        if isinstance(a.class_type, ContainerType))
+                        if isinstance(a.class_type, NumpyNDArrayType))
         if len(possible_class_types) == 0:
             class_type = dtype
         elif len(possible_class_types) == 1:
diff --git a/pyccel/codegen/printing/ccode.py b/pyccel/codegen/printing/ccode.py
index caf536db..b8732453 100644
--- a/pyccel/codegen/printing/ccode.py
+++ b/pyccel/codegen/printing/ccode.py
@@ -1045,14 +1045,26 @@ class CCodePrinter(CodePrinter):
         return ' || '.join(a for a in args)
 
     def _print_PyccelEq(self, expr):
-        lhs = self._print(expr.args[0])
-        rhs = self._print(expr.args[1])
-        return '{0} == {1}'.format(lhs, rhs)
+        lhs, rhs = expr.args
+        if isinstance(lhs.class_type, StringType) and isinstance(rhs.class_type, StringType):
+            lhs_code = self._print(CStrStr(lhs))
+            rhs_code = self._print(CStrStr(rhs))
+            return f'!strcmp({lhs_code}, {rhs_code})'
+        else:
+            lhs_code = self._print(lhs)
+            rhs_code = self._print(rhs)
+            return f'{lhs_code} == {rhs_code}'
 
     def _print_PyccelNe(self, expr):
-        lhs = self._print(expr.args[0])
-        rhs = self._print(expr.args[1])
-        return '{0} != {1}'.format(lhs, rhs)
+        lhs, rhs = expr.args
+        if isinstance(lhs.class_type, StringType) and isinstance(rhs.class_type, StringType):
+            lhs_code = self._print(CStrStr(lhs))
+            rhs_code = self._print(CStrStr(rhs))
+            return f'strcmp({lhs_code}, {rhs_code})'
+        else:
+            lhs_code = self._print(lhs)
+            rhs_code = self._print(rhs)
+            return f'{lhs_code} != {rhs_code}'
 
     def _print_PyccelLt(self, expr):
         lhs = self._print(expr.args[0])
diff --git a/pyccel/codegen/printing/fcode.py b/pyccel/codegen/printing/fcode.py
index fef257ba..7d794dab 100644
--- a/pyccel/codegen/printing/fcode.py
+++ b/pyccel/codegen/printing/fcode.py
@@ -2270,7 +2270,7 @@ class FCodePrinter(CodePrinter):
                 return f'call {var_code} % reserve({size_code})\n'
             else:
                 return ''
-        elif isinstance(class_type, (HomogeneousContainerType, DictType)):
+        elif isinstance(class_type, (HomogeneousContainerType, DictType, StringType)):
             return ''
 
         else:


Test patch:
diff --git a/tests/epyccel/test_strings.py b/tests/epyccel/test_strings.py
index 5c2e540f..676c3da2 100644
--- a/tests/epyccel/test_strings.py
+++ b/tests/epyccel/test_strings.py
@@ -26,3 +26,19 @@ def test_strings(test_func, language):
     print(python_out)
     print(pyccel_out)
     assert python_out == pyccel_out
+
+def test_string_compare(language):
+    def str_comp():
+        a = 'hello'
+        if a == 'world':
+            return 1
+        elif a != 'boo':
+            return 2
+        elif a == 'hello':
+            return 3
+        else:
+            return 4
+
+    f = epyccel( str_comp, language=language )
+
+    assert str_comp() == f()