Repository: numba/numba

Issue:
support `__mul__` for strings
<!--

Thanks for opening an issue! To help the Numba team handle your information
efficiently, please first ensure that there is no other issue present that
already describes the issue you have
(search at https://github.com/numba/numba/issues?&q=is%3Aissue).

For more general "how do I do X?" type questions, please speak to us in real
time on https://gitter.im/numba/numba or post to the Numba mailing list
https://groups.google.com/a/continuum.io/forum/#!forum/numba-users.

-->

## Feature request

As title. This sort of thing should work:

```python
'abc' * 5
'abc'.__mul__(5) 
```


<!--

Please include details of the feature you would like to see, why you would
like to see it/the use case

-->



Discussion:
@stuartarchibald, if this is still open I can make a PR.

Resolving patch:
diff --git a/numba/unicode.py b/numba/unicode.py
--- a/numba/unicode.py
+++ b/numba/unicode.py
@@ -13,6 +13,7 @@
     overload,
     overload_method,
     intrinsic,
+    register_jitable,
 )
 from numba.targets.imputils import (lower_constant, lower_cast, lower_builtin,
                                     iternext_impl, impl_ret_new_ref, RefType)
@@ -745,6 +746,45 @@ def concat_impl(a, b):
         return concat_impl
 
 
+@register_jitable
+def _repeat_impl(str_arg, mult_arg):
+    if str_arg == '' or mult_arg < 1:
+        return ''
+    elif mult_arg == 1:
+        return str_arg
+    else:
+        new_length = str_arg._length * mult_arg
+        new_kind = str_arg._kind
+        result = _empty_string(new_kind, new_length)
+        # make initial copy into result
+        len_a = len(str_arg)
+        _strncpy(result, 0, str_arg, 0, len_a)
+        # loop through powers of 2 for efficient copying
+        copy_size = len_a
+        while 2 * copy_size <= new_length:
+            _strncpy(result, copy_size, result, 0, copy_size)
+            copy_size *= 2
+
+        if not 2 * copy_size == new_length:
+            # if copy_size not an exact multiple it then needs
+            # to complete the rest of the copies
+            rest = new_length - copy_size
+            _strncpy(result, copy_size, result, copy_size - rest, rest)
+            return result
+
+
+@overload(operator.mul)
+def unicode_repeat(a, b):
+    if isinstance(a, types.UnicodeType) and isinstance(b, types.Integer):
+        def wrap(a, b):
+            return _repeat_impl(a, b)
+        return wrap
+    elif isinstance(a, types.Integer) and isinstance(b, types.UnicodeType):
+        def wrap(a, b):
+            return _repeat_impl(b, a)
+        return wrap
+
+
 @lower_builtin('getiter', types.UnicodeType)
 def getiter_unicode(context, builder, sig, args):
     [ty] = sig.args


Test patch:
diff --git a/numba/tests/test_unicode.py b/numba/tests/test_unicode.py
--- a/numba/tests/test_unicode.py
+++ b/numba/tests/test_unicode.py
@@ -40,6 +40,10 @@ def concat_usecase(x, y):
     return x + y
 
 
+def repeat_usecase(x, y):
+    return x * y
+
+
 def inplace_concat_usecase(x, y):
     x += y
     return x
@@ -362,6 +366,23 @@ def test_concat(self, flags=no_pyobj_flags):
                                  cfunc(a, b),
                                  "'%s' + '%s'?" % (a, b))
 
+    def test_repeat(self, flags=no_pyobj_flags):
+        pyfunc = repeat_usecase
+        cfunc = njit(pyfunc)
+        for a in UNICODE_EXAMPLES + ['']:
+            for b in (-1, 0, 1, 2, 3, 4, 5, 7, 8, 15, 70):
+                self.assertEqual(pyfunc(a, b),
+                                 cfunc(a, b))
+                self.assertEqual(pyfunc(b, a),
+                                 cfunc(b, a))
+
+    def test_repeat_exception_float(self):
+        self.disable_leak_check()
+        cfunc = njit(repeat_usecase)
+        with self.assertRaises(TypingError) as raises:
+            cfunc('hi', 2.5)
+        self.assertIn('Invalid use of Function(<built-in function mul>)', str(raises.exception))
+
     def test_split_exception_empty_sep(self):
         self.disable_leak_check()