Repository: pyccel/pyccel (python)

Issue:
Remove string length limitation
### Describe the feature
Currently the type of a local string is printed as `character(len=280)`, it should be possible to replace this with `character(len=:), allocatable`


Pull request description:
Stop limiting the length of strings to 128 characters in Fortran
Correct the declaration type of strings in Fortran to remove the limit of 128 characters. Fixes #2013 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 99b24a65..97ae36c3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -76,6 +76,7 @@ All notable changes to this project will be documented in this file.
 -   #2026 : Fix missing loop in slice assignment.
 -   #2008 : Ensure list/set/dict assignment is recognised as a reference.
 -   #2039 : Ensure any expressions in the iterable of a for loop are calculated before the loop.
+-   #2013 : Stop limiting the length of strings to 128 characters.
 
 ### Changed
 
diff --git a/docs/builtin-functions.md b/docs/builtin-functions.md
index ee0c9ca2..38358c4f 100644
--- a/docs/builtin-functions.md
+++ b/docs/builtin-functions.md
@@ -132,3 +132,54 @@ Python contains a limited number of builtin functions defined [here](https://doc
 | `update` | No |
 | `values` | No |
 
+## String methods
+
+| Method | Supported |
+|----------|-----------|
+| `capitalize` | No |
+| `casefold` | No |
+| `center` | No |
+| `count` | No |
+| `encode` | No |
+| `endswith` | No |
+| `expandtabs` | No |
+| `find` | No |
+| `format` | No |
+| `format_map` | No |
+| `index` | No |
+| `isalnum` | No |
+| `isalpha` | No |
+| `isascii` | No |
+| `isdecimal` | No |
+| `isdigit` | No |
+| `isidentifier` | No |
+| `islower` | No |
+| `isnumeric` | No |
+| `isprintable` | No |
+| `isspace` | No |
+| `istitle` | No |
+| `isupper` | No |
+| `join` | No |
+| `ljust` | No |
+| `lower` | No |
+| `lstrip` | No |
+| `make_trans` | No |
+| `partition` | No |
+| `removeprefix` | No |
+| `removesufix` | No |
+| `replace` | No |
+| `rfind` | No |
+| `rindex` | No |
+| `rjust` | No |
+| `rpartition` | No |
+| `rsplit` | No |
+| `rstrip` | No |
+| `split` | No |
+| `splitlines` | No |
+| `startswith` | No |
+| `strip` | No |
+| `swapcase` | No |
+| `title` | No |
+| `translate` | No |
+| `upper` | No |
+| `zfill` | No |
diff --git a/docs/type_annotations.md b/docs/type_annotations.md
index ee7b16b6..097e84c8 100644
--- a/docs/type_annotations.md
+++ b/docs/type_annotations.md
@@ -59,6 +59,10 @@ c : dict[int,complex] = {}
 ```
 So far strings are supported as keys however as Pyccel is still missing support for non-literal strings it remains to be seen how such cases will be handled in low-level languages.
 
+## Strings
+
+Pyccel contains very minimal support for strings. This is mostly provided to allow the use of strings as keys of dictionaries.
+
 ## Handling multiple types
 
 The basic type annotations indicate only one type however it is common to need a function to be able to handle multiple types, e.g. integers and floats. In this case it is possible to provide a union type.
diff --git a/pyccel/ast/builtins.py b/pyccel/ast/builtins.py
index 34787432..0aaaf5c0 100644
--- a/pyccel/ast/builtins.py
+++ b/pyccel/ast/builtins.py
@@ -633,7 +633,10 @@ class PythonLen(PyccelFunction):
     __slots__ = ()
 
     def __new__(cls, arg):
-        return arg.shape[0]
+        if isinstance(arg, LiteralString):
+            return LiteralInteger(len(arg.python_value))
+        else:
+            return arg.shape[0]
 
 #==============================================================================
 class PythonList(TypedAstNode):
diff --git a/pyccel/codegen/printing/fcode.py b/pyccel/codegen/printing/fcode.py
index 56fd42e4..24491487 100644
--- a/pyccel/codegen/printing/fcode.py
+++ b/pyccel/codegen/printing/fcode.py
@@ -1641,6 +1641,8 @@ class FCodePrinter(CodePrinter):
             return f'size({arg_code}, {index}, {prec})'
         elif isinstance(arg.class_type, (HomogeneousListType, HomogeneousSetType, DictType)):
             return f'{arg_code} % size()'
+        elif isinstance(arg.class_type, StringType):
+            return f'len({arg_code})'
         else:
             raise NotImplementedError(f"Don't know how to represent shape of object of type {arg.class_type}")
 
@@ -1900,8 +1902,9 @@ class FCodePrinter(CodePrinter):
             dtype_str = self._print(dtype)
 
             if intent_in:
-                dtype_str = dtype_str[:9] +'(len =*)'
-                #TODO improve ,this is the case of character as argument
+                dtype_str += '(len = *)'
+            else:
+                dtype_str += '(len = :)'
         elif isinstance(dtype, BindCPointer):
             dtype_str = 'type(c_ptr)'
             self._constantImports.setdefault('ISO_C_Binding', set()).add('c_ptr')
@@ -1934,11 +1937,11 @@ class FCodePrinter(CodePrinter):
                 intentstr = f', intent({intent})'
 
         # Compute allocatable string
-        if not is_static and not is_string:
+        if not is_static:
             if is_alias:
                 allocatablestr = ', pointer'
 
-            elif on_heap and not intent_in and isinstance(var.class_type, (NumpyNDArrayType, HomogeneousTupleType)):
+            elif on_heap and not intent_in and isinstance(var.class_type, (NumpyNDArrayType, HomogeneousTupleType, StringType)):
                 allocatablestr = ', allocatable'
 
             # ISSUES #177: var is allocatable and target
@@ -2204,8 +2207,7 @@ class FCodePrinter(CodePrinter):
         return 'complex'
 
     def _print_StringType(self, expr):
-        return 'character(len=280)'
-        #TODO fix improve later
+        return 'character'
 
     def _print_FixedSizeNumericType(self, expr):
         return f'{self._print(expr.primitive_type)}{expr.precision}'


Test patch:
diff --git a/tests/epyccel/test_builtins.py b/tests/epyccel/test_builtins.py
index 4ff9ff96..ddb41067 100644
--- a/tests/epyccel/test_builtins.py
+++ b/tests/epyccel/test_builtins.py
@@ -477,3 +477,38 @@ def test_len_dict_int_float(stc_language):
     epyc_f = epyccel(f, language=stc_language)
 
     assert epyc_f() == f()
+
+@pytest.mark.parametrize( 'language', (
+        pytest.param("fortran", marks = pytest.mark.fortran),
+        pytest.param("c", marks = [
+            pytest.mark.skip(reason="C has no support for strings. See #2061"),
+            pytest.mark.c]),
+        pytest.param("python", marks = pytest.mark.python)
+    )
+)
+def test_len_string(language):
+    def f():
+        a = 'abcdefghij'
+        b = len(a)
+        return b
+
+    epyc_f = epyccel(f, language = language)
+
+    assert epyc_f() == f()
+
+@pytest.mark.parametrize( 'language', (
+        pytest.param("fortran", marks = pytest.mark.fortran),
+        pytest.param("c", marks = [
+            pytest.mark.skip(reason="C has no support for strings. See #2061"),
+            pytest.mark.c]),
+        pytest.param("python", marks = pytest.mark.python)
+    )
+)
+def test_len_literal_string(language):
+    def f():
+        b = len('abcd')
+        return b
+
+    epyc_f = epyccel(f, language = language)
+
+    assert epyc_f() == f()
diff --git a/tests/epyccel/test_epyccel_variable_annotations.py b/tests/epyccel/test_epyccel_variable_annotations.py
index a4b9bdef..bb616757 100644
--- a/tests/epyccel/test_epyccel_variable_annotations.py
+++ b/tests/epyccel/test_epyccel_variable_annotations.py
@@ -330,3 +330,19 @@ def test_dict_complex_float(language):
 
     epyc_dict_int_float = epyccel(dict_int_float, language = language)
     assert epyc_dict_int_float() == dict_int_float()
+
+@pytest.mark.parametrize( 'language', (
+        pytest.param("fortran", marks = pytest.mark.fortran),
+        pytest.param("c", marks = [
+            pytest.mark.skip(reason="C has no support for strings. See #2061"),
+            pytest.mark.c]),
+        pytest.param("python", marks = pytest.mark.python)
+    )
+)
+def test_str_declaration(language):
+    def str_declaration():
+        a : str = 'hello here is a very long string with more than 128 characters. This used to be a Fortran limit but now I can hold lots more characters. There is no limit!'
+        return len(a)
+
+    epyc_str_declaration = epyccel(str_declaration, language = language)
+    assert str_declaration() == epyc_str_declaration()