Repository: numba/numba

Issue:
typed-list fails to refine via extend on unicode input
## Reporting a bug

- [x] I am using the latest released version of Numba (most recent is visible in
 the change log (https://github.com/numba/numba/blob/master/CHANGE_LOG).
- [x] I have included below a minimal working reproducer (if you are unsure how
 to write one see http://matthewrocklin.com/blog/work/2018/02/28/minimal-bug-reports).

Reproducer:

```python
from numba import njit
from numba.typed import List


@njit
def foo():
    l = List()
    l.extend("abc")
    return l


print(foo())
```

Stacktrace:

```
Traceback (most recent call last):
  File "/Users/vhaenel/git/numba/numba/core/typeinfer.py", line 149, in propagate
    constraint(typeinfer)
  File "/Users/vhaenel/git/numba/numba/core/typeinfer.py", line 486, in __call__
    self.resolve(typeinfer, typevars, fnty)
  File "/Users/vhaenel/git/numba/numba/core/typeinfer.py", line 506, in resolve
    sig = typeinfer.resolve_call(fnty, pos_args, kw_args)
  File "/Users/vhaenel/git/numba/numba/core/typeinfer.py", line 1440, in resolve_call
    return self.context.resolve_function_type(fnty, pos_args, kw_args)
  File "/Users/vhaenel/git/numba/numba/core/typing/context.py", line 214, in resolve_function_type
    raise last_exception
  File "/Users/vhaenel/git/numba/numba/core/typing/context.py", line 197, in resolve_function_type
    res = self._resolve_user_function_type(func, args, kws)
  File "/Users/vhaenel/git/numba/numba/core/typing/context.py", line 249, in _resolve_user_function_type
    return func.get_call_type(self, args, kws)
  File "/Users/vhaenel/git/numba/numba/core/types/functions.py", line 215, in get_call_type
    out = template.apply(args, kws)
  File "/Users/vhaenel/git/numba/numba/core/typing/templates.py", line 246, in apply
    sig = generic(args, kws)
  File "/Users/vhaenel/git/numba/numba/core/typing/templates.py", line 802, in generic
    sig = self._get_signature(self.context, fnty, args, kws)
  File "/Users/vhaenel/git/numba/numba/core/typing/templates.py", line 753, in _get_signature
    sig = fnty.get_call_type(typingctx, args, kws)
  File "/Users/vhaenel/git/numba/numba/core/types/functions.py", line 148, in get_call_type
    failures.raise_error()
  File "/Users/vhaenel/git/numba/numba/core/types/functions.py", line 77, in raise_error
    raise errors.TypingError(self.format())
numba.core.errors.TypingError: Invalid use of Function(<function impl_extend at 0x117b72b80>) with argument(s) of type(s): (ListType[undefined], unicode_type)
 * parameterized
In definition 0:
    TypingError: unable to extend list, iterable is missing either *dtype*, *item_type* or *yield_type*.
    raised from /Users/vhaenel/git/numba/numba/typed/listobject.py:1053
In definition 1:
    TypingError: unable to extend list, iterable is missing either *dtype*, *item_type* or *yield_type*.
    raised from /Users/vhaenel/git/numba/numba/typed/listobject.py:1053
This error is usually caused by passing an argument of a type that is unsupported by the named function.
[1] During: resolving callee type: BoundFunction((<class 'numba.core.types.containers.ListType'>, 'extend') for ListType[undefined])
[2] During: typing of call at foo/foo134.py (8)


The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "foo/foo134.py", line 12, in <module>
    print(foo())
  File "/Users/vhaenel/git/numba/numba/core/dispatcher.py", line 401, in _compile_for_args
    error_rewrite(e, 'typing')
  File "/Users/vhaenel/git/numba/numba/core/dispatcher.py", line 342, in error_rewrite
    raise e
  File "/Users/vhaenel/git/numba/numba/core/dispatcher.py", line 353, in _compile_for_args
    return self.compile(tuple(argtypes))
  File "/Users/vhaenel/git/numba/numba/core/compiler_lock.py", line 32, in _acquire_compile_lock
    return func(*args, **kwargs)
  File "/Users/vhaenel/git/numba/numba/core/dispatcher.py", line 768, in compile
    cres = self._compiler.compile(args, return_type)
  File "/Users/vhaenel/git/numba/numba/core/dispatcher.py", line 81, in compile
    raise retval
  File "/Users/vhaenel/git/numba/numba/core/dispatcher.py", line 91, in _compile_cached
    retval = self._compile_core(args, return_type)
  File "/Users/vhaenel/git/numba/numba/core/dispatcher.py", line 104, in _compile_core
    cres = compiler.compile_extra(self.targetdescr.typing_context,
  File "/Users/vhaenel/git/numba/numba/core/compiler.py", line 548, in compile_extra
    return pipeline.compile_extra(func)
  File "/Users/vhaenel/git/numba/numba/core/compiler.py", line 328, in compile_extra
    return self._compile_bytecode()
  File "/Users/vhaenel/git/numba/numba/core/compiler.py", line 390, in _compile_bytecode
    return self._compile_core()
  File "/Users/vhaenel/git/numba/numba/core/compiler.py", line 370, in _compile_core
    raise e
  File "/Users/vhaenel/git/numba/numba/core/compiler.py", line 361, in _compile_core
    pm.run(self.state)
  File "/Users/vhaenel/git/numba/numba/core/compiler_machinery.py", line 335, in run
    raise patched_exception
  File "/Users/vhaenel/git/numba/numba/core/compiler_machinery.py", line 326, in run
    self._runPass(idx, pass_inst, state)
  File "/Users/vhaenel/git/numba/numba/core/compiler_lock.py", line 32, in _acquire_compile_lock
    return func(*args, **kwargs)
  File "/Users/vhaenel/git/numba/numba/core/compiler_machinery.py", line 290, in _runPass
    mutated |= check(pss.run_pass, internal_state)
  File "/Users/vhaenel/git/numba/numba/core/compiler_machinery.py", line 263, in check
    mangled = func(compiler_state)
  File "/Users/vhaenel/git/numba/numba/core/typed_passes.py", line 89, in run_pass
    typemap, return_type, calltypes = type_inference_stage(
  File "/Users/vhaenel/git/numba/numba/core/typed_passes.py", line 67, in type_inference_stage
    infer.propagate(raise_errors=raise_errors)
  File "/Users/vhaenel/git/numba/numba/core/typeinfer.py", line 985, in propagate
    raise errors[0]
numba.core.errors.TypingError: Failed in nopython mode pipeline (step: nopython frontend)
Invalid use of Function(<function impl_extend at 0x117b72b80>) with argument(s) of type(s): (ListType[undefined], unicode_type)
 * parameterized
In definition 0:
    TypingError: unable to extend list, iterable is missing either *dtype*, *item_type* or *yield_type*.
    raised from /Users/vhaenel/git/numba/numba/typed/listobject.py:1053
In definition 1:
    TypingError: unable to extend list, iterable is missing either *dtype*, *item_type* or *yield_type*.
    raised from /Users/vhaenel/git/numba/numba/typed/listobject.py:1053
This error is usually caused by passing an argument of a type that is unsupported by the named function.
[1] During: resolving callee type: BoundFunction((<class 'numba.core.types.containers.ListType'>, 'extend') for ListType[undefined])
[2] During: typing of call at foo/foo134.py (8)


File "foo/foo134.py", line 8:
def foo():
    <source elided>
    l = List()
    l.extend("abc")
```

Discussion:

Patch:
diff --git a/numba/typed/listobject.py b/numba/typed/listobject.py
index 4f4071e51..379f12aba 100644
--- a/numba/typed/listobject.py
+++ b/numba/typed/listobject.py
@@ -1049,6 +1049,8 @@ def impl_extend(l, iterable):
             ty = iterable.item_type
         elif hasattr(iterable, "yield_type"):  # iterators and generators
             ty = iterable.yield_type
+        elif isinstance(iterable, types.UnicodeType):
+            ty = iterable
         else:
             raise TypingError("unable to extend list, iterable is missing "
                               "either *dtype*, *item_type* or *yield_type*.")


Test patch:
diff --git a/numba/tests/test_typedlist.py b/numba/tests/test_typedlist.py
index 9f26b1327..6ab938601 100644
--- a/numba/tests/test_typedlist.py
+++ b/numba/tests/test_typedlist.py
@@ -106,6 +106,17 @@ class TestTypedList(MemoryLeakMixin, TestCase):
         # index
         self.assertEqual(l.index(15), 4)
 
+    def test_list_extend_refines_on_unicode_type(self):
+        @njit
+        def foo(string):
+            l = List()
+            l.extend(string)
+            return l
+
+        for func in (foo, foo.py_func):
+            for string in ("a", "abc", "\nabc\t"):
+                self.assertEqual(list(func(string)), list(string))
+
     def test_unsigned_access(self):
         L = List.empty_list(int32)
         ui32_0 = types.uint32(0)