from __future__ import print_function, division, absolute_import

import numpy as np

from collections import namedtuple

from numba import types, utils
from numba.typing.templates import (AttributeTemplate, AbstractTemplate,
                                    infer, infer_getattr, signature,
                                    bound_function)
# import time side effect: array operations requires typing support of sequence
# defined in collections: e.g. array.shape[i]
from numba.typing import collections
from numba.errors import TypingError

Indexing = namedtuple("Indexing", ("index", "result", "advanced"))


def get_array_index_type(ary, idx):
    """
    Returns None or a tuple-3 for the types of the input array, index, and
    resulting type of ``array[index]``.

    Note: This is shared logic for ndarray getitem and setitem.
    """
    if not isinstance(ary, types.Buffer):
        return

    ndim = ary.ndim

    left_indices = []
    right_indices = []
    ellipsis_met = False
    advanced = False
    has_integer = False

    if not isinstance(idx, types.BaseTuple):
        idx = [idx]

    # Walk indices
    for ty in idx:
        if ty is types.ellipsis:
            if ellipsis_met:
                raise TypeError("only one ellipsis allowed in array index "
                                "(got %s)" % (idx,))
            ellipsis_met = True
        elif isinstance(ty, types.SliceType):
            pass
        elif isinstance(ty, types.Integer):
            # Normalize integer index
            ty = types.intp if ty.signed else types.uintp
            # Integer indexing removes the given dimension
            ndim -= 1
            has_integer = True
        elif (isinstance(ty, types.Array) and ty.ndim == 0
              and isinstance(ty.dtype, types.Integer)):
            # 0-d array used as integer index
            ndim -= 1
            has_integer = True
        elif (isinstance(ty, types.Array)
              and ty.ndim == 1
              and isinstance(ty.dtype, (types.Integer, types.Boolean))):
            if advanced or has_integer:
                # We don't support the complicated combination of
                # advanced indices (and integers are considered part
                # of them by Numpy).
                raise NotImplementedError("only one advanced index supported")
            advanced = True
        else:
            raise TypeError("unsupported array index type %s in %s"
                            % (ty, idx))
        (right_indices if ellipsis_met else left_indices).append(ty)

    # Only Numpy arrays support advanced indexing
    if advanced and not isinstance(ary, types.Array):
        return

    # Check indices and result dimensionality
    all_indices = left_indices + right_indices
    if ellipsis_met:
        assert right_indices[0] is types.ellipsis
        del right_indices[0]

    n_indices = len(all_indices) - ellipsis_met
    if n_indices > ary.ndim:
        raise TypeError("cannot index %s with %d indices: %s"
                        % (ary, n_indices, idx))
    if n_indices == ary.ndim and ndim == 0 and not ellipsis_met:
        # Full integer indexing => scalar result
        # (note if ellipsis is present, a 0-d view is returned instead)
        res = ary.dtype

    elif advanced:
        # Result is a copy
        res = ary.copy(ndim=ndim, layout='C', readonly=False)

    else:
        # Result is a view
        if ary.slice_is_copy:
            # Avoid view semantics when the original type creates a copy
            # when slicing.
            return

        # Infer layout
        layout = ary.layout

        def keeps_contiguity(ty, is_innermost):
            # A slice can only keep an array contiguous if it is the
            # innermost index and it is not strided
            return (ty is types.ellipsis or isinstance(ty, types.Integer)
                    or (is_innermost and isinstance(ty, types.SliceType)
                        and not ty.has_step))

        def check_contiguity(outer_indices):
            """
            Whether indexing with the given indices (from outer to inner in
            physical layout order) can keep an array contiguous.
            """
            for ty in outer_indices[:-1]:
                if not keeps_contiguity(ty, False):
                    return False
            if outer_indices and not keeps_contiguity(outer_indices[-1], True):
                return False
            return True

        if layout == 'C':
            # Integer indexing on the left keeps the array C-contiguous
            if n_indices == ary.ndim:
                # If all indices are there, ellipsis's place is indifferent
                left_indices = left_indices + right_indices
                right_indices = []
            if right_indices:
                layout = 'A'
            elif not check_contiguity(left_indices):
                layout = 'A'
        elif layout == 'F':
            # Integer indexing on the right keeps the array F-contiguous
            if n_indices == ary.ndim:
                # If all indices are there, ellipsis's place is indifferent
                right_indices = left_indices + right_indices
                left_indices = []
            if left_indices:
                layout = 'A'
            elif not check_contiguity(right_indices[::-1]):
                layout = 'A'

        res = ary.copy(ndim=ndim, layout=layout)

    # Re-wrap indices
    if isinstance(idx, types.BaseTuple):
        idx = types.BaseTuple.from_types(all_indices)
    else:
        idx, = all_indices

    return Indexing(idx, res, advanced)


@infer
class GetItemBuffer(AbstractTemplate):
    key = "getitem"

    def generic(self, args, kws):
        assert not kws
        [ary, idx] = args
        out = get_array_index_type(ary, idx)
        if out is not None:
            return signature(out.result, ary, out.index)

@infer
class SetItemBuffer(AbstractTemplate):
    key = "setitem"

    def generic(self, args, kws):
        assert not kws
        ary, idx, val = args
        if not isinstance(ary, types.Buffer):
            return
        if not ary.mutable:
            raise TypeError("Cannot modify value of type %s" %(ary,))
        out = get_array_index_type(ary, idx)
        if out is None:
            return

        idx = out.index
        res = out.result
        if isinstance(res, types.Array):
            # Indexing produces an array
            if isinstance(val, types.Array):
                if not self.context.can_convert(val.dtype, res.dtype):
                    # DType conversion not possible
                    return
                else:
                    res = val
            elif isinstance(val, types.Sequence):
                if (res.ndim == 1 and
                    self.context.can_convert(val.dtype, res.dtype)):
                    # Allow assignement of sequence to 1d array
                    res = val
                else:
                    # NOTE: sequence-to-array broadcasting is unsupported
                    return
            else:
                # Allow scalar broadcasting
                if self.context.can_convert(val, res.dtype):
                    res = res.dtype
                else:
                    # Incompatible scalar type
                    return
        elif not isinstance(val, types.Array):
            # Single item assignment
            if not self.context.can_convert(val, res):
                # if the array dtype is not yet defined
                if not res.is_precise():
                    # set the array type to use the dtype of value (RHS)
                    newary = ary.copy(dtype=val)
                    return signature(types.none, newary, idx, res)
                else:
                    return
            res = val
        else:
            return
        return signature(types.none, ary, idx, res)


def normalize_shape(shape):
    if isinstance(shape, types.UniTuple):
        if isinstance(shape.dtype, types.Integer):
            dimtype = types.intp if shape.dtype.signed else types.uintp
            return types.UniTuple(dimtype, len(shape))

    elif isinstance(shape, types.Tuple) and shape.count == 0:
        # Force (0 x intp) for consistency with other shapes
        return types.UniTuple(types.intp, 0)


@infer_getattr
class ArrayAttribute(AttributeTemplate):
    key = types.Array

    def resolve_dtype(self, ary):
        return types.DType(ary.dtype)

    def resolve_itemsize(self, ary):
        return types.intp

    def resolve_shape(self, ary):
        return types.UniTuple(types.intp, ary.ndim)

    def resolve_strides(self, ary):
        return types.UniTuple(types.intp, ary.ndim)

    def resolve_ndim(self, ary):
        return types.intp

    def resolve_size(self, ary):
        return types.intp

    def resolve_flat(self, ary):
        return types.NumpyFlatType(ary)

    def resolve_ctypes(self, ary):
        return types.ArrayCTypes(ary)

    def resolve_flags(self, ary):
        return types.ArrayFlags(ary)

    def resolve_T(self, ary):
        if ary.ndim <= 1:
            retty = ary
        else:
            layout = {"C": "F", "F": "C"}.get(ary.layout, "A")
            retty = ary.copy(layout=layout)
        return retty

    def resolve_real(self, ary):
        return self._resolve_real_imag(ary, attr='real')

    def resolve_imag(self, ary):
        return self._resolve_real_imag(ary, attr='imag')

    def _resolve_real_imag(self, ary, attr):
        if ary.dtype in types.complex_domain:
            return ary.copy(dtype=ary.dtype.underlying_float, layout='A')
        elif ary.dtype in types.number_domain:
            res = ary.copy(dtype=ary.dtype)
            if attr == 'imag':
                res = res.copy(readonly=True)
            return res
        else:
            msg = "cannot access .{} of array of {}"
            raise TypingError(msg.format(attr, ary.dtype))

    @bound_function("array.transpose")
    def resolve_transpose(self, ary, args, kws):
        def sentry_shape_scalar(ty):
            if ty in types.number_domain:
                # Guard against non integer type
                if not isinstance(ty, types.Integer):
                    raise TypeError("transpose() arg cannot be {0}".format(ty))
                return True
            else:
                return False

        assert not kws
        if len(args) == 0:
            return signature(self.resolve_T(ary))

        if len(args) == 1:
            shape, = args

            if sentry_shape_scalar(shape):
                assert ary.ndim == 1
                return signature(ary, *args)

            shape = normalize_shape(shape)
            if shape is None:
                return

            assert ary.ndim == shape.count
            return signature(self.resolve_T(ary), shape)

        else:
            if any(not sentry_shape_scalar(a) for a in args):
                raise TypeError("transpose({0}) is not supported".format(
                    ', '.join(args)))
            assert ary.ndim == len(args)
            return signature(self.resolve_T(ary), *args)

    @bound_function("array.copy")
    def resolve_copy(self, ary, args, kws):
        assert not args
        assert not kws
        retty = ary.copy(layout="C", readonly=False)
        return signature(retty)

    @bound_function("array.item")
    def resolve_item(self, ary, args, kws):
        assert not kws
        # We don't support explicit arguments as that's exactly equivalent
        # to regular indexing.  The no-argument form is interesting to
        # allow some degree of genericity when writing functions.
        if not args:
            return signature(ary.dtype)

    @bound_function("array.itemset")
    def resolve_itemset(self, ary, args, kws):
        assert not kws
        # We don't support explicit arguments as that's exactly equivalent
        # to regular indexing.  The no-argument form is interesting to
        # allow some degree of genericity when writing functions.
        if len(args) == 1:
            return signature(types.none, ary.dtype)

    @bound_function("array.nonzero")
    def resolve_nonzero(self, ary, args, kws):
        assert not args
        assert not kws
        # 0-dim arrays return one result array
        ndim = max(ary.ndim, 1)
        retty = types.UniTuple(types.Array(types.intp, 1, 'C'), ndim)
        return signature(retty)

    @bound_function("array.reshape")
    def resolve_reshape(self, ary, args, kws):
        def sentry_shape_scalar(ty):
            if ty in types.number_domain:
                # Guard against non integer type
                if not isinstance(ty, types.Integer):
                    raise TypeError("reshape() arg cannot be {0}".format(ty))
                return True
            else:
                return False

        assert not kws
        if ary.layout not in 'CF':
            # only work for contiguous array
            raise TypeError("reshape() supports contiguous array only")

        if len(args) == 1:
            # single arg
            shape, = args

            if sentry_shape_scalar(shape):
                ndim = 1
            else:
                shape = normalize_shape(shape)
                if shape is None:
                    return
                ndim = shape.count
            retty = ary.copy(ndim=ndim)
            return signature(retty, shape)

        elif len(args) == 0:
            # no arg
            raise TypeError("reshape() take at least one arg")

        else:
            # vararg case
            if any(not sentry_shape_scalar(a) for a in args):
                raise TypeError("reshape({0}) is not supported".format(
                    ', '.join(args)))

            retty = ary.copy(ndim=len(args))
            return signature(retty, *args)

    @bound_function("array.sort")
    def resolve_sort(self, ary, args, kws):
        assert not args
        assert not kws
        if ary.ndim == 1:
            return signature(types.none)

    @bound_function("array.argsort")
    def resolve_argsort(self, ary, args, kws):
        assert not args
        kwargs = dict(kws)
        kind = kwargs.pop('kind', types.Const('quicksort'))
        if kwargs:
            msg = "Unsupported keywords: {!r}"
            raise TypingError(msg.format([k for k in kwargs.keys()]))
        if ary.ndim == 1:
            def argsort_stub(kind='quicksort'):
                pass
            pysig = utils.pysignature(argsort_stub)
            sig = signature(types.Array(types.intp, 1, 'C'), kind).replace(pysig=pysig)
            return sig

    @bound_function("array.view")
    def resolve_view(self, ary, args, kws):
        from .npydecl import _parse_dtype
        assert not kws
        dtype, = args
        dtype = _parse_dtype(dtype)
        if dtype is None:
            return
        retty = ary.copy(dtype=dtype)
        return signature(retty, *args)

    @bound_function("array.astype")
    def resolve_astype(self, ary, args, kws):
        from .npydecl import _parse_dtype
        assert not kws
        dtype, = args
        dtype = _parse_dtype(dtype)
        if dtype is None:
            return
        if not self.context.can_convert(ary.dtype, dtype):
            raise TypeError("astype(%s) not supported on %s: "
                            "cannot convert from %s to %s"
                            % (dtype, ary, ary.dtype, dtype))
        layout = ary.layout if ary.layout in 'CF' else 'C'
        retty = ary.copy(dtype=dtype, layout=layout)
        return signature(retty, *args)

    @bound_function("array.ravel")
    def resolve_ravel(self, ary, args, kws):
        # Only support no argument version (default order='C')
        assert not kws
        assert not args
        return signature(ary.copy(ndim=1, layout='C'))

    @bound_function("array.flatten")
    def resolve_flatten(self, ary, args, kws):
        # Only support no argument version (default order='C')
        assert not kws
        assert not args
        return signature(ary.copy(ndim=1, layout='C'))

    @bound_function("array.take")
    def resolve_take(self, ary, args, kws):
        assert not kws
        argty, = args
        if isinstance(argty, types.Integer):
            sig = signature(ary.dtype, *args)
        elif isinstance(argty, types.Array):
            sig = signature(argty.copy(layout='C', dtype=ary.dtype), *args)
        elif isinstance(argty, types.List): # 1d lists only
            sig = signature(types.Array(ary.dtype, 1, 'C'), *args)
        elif isinstance(argty, types.BaseTuple):
            sig = signature(types.Array(ary.dtype, np.ndim(argty), 'C'), *args)
        else:
            raise TypeError("take(%s) not supported for %s" % argty)
        return sig

    def generic_resolve(self, ary, attr):
        # Resolution of other attributes, for record arrays
        if isinstance(ary.dtype, types.Record):
            if attr in ary.dtype.fields:
                return ary.copy(dtype=ary.dtype.typeof(attr), layout='A')


@infer_getattr
class DTypeAttr(AttributeTemplate):
    key = types.DType

    def resolve_type(self, ary):
        # Wrap the numeric type in NumberClass
        return types.NumberClass(ary.dtype)

    def resolve_kind(self, ary):
        if isinstance(ary.key, types.scalars.Float):
            val = 'f'
        elif isinstance(ary.key, types.scalars.Integer):
            val = 'i'
        else:
            return None  # other types not supported yet
        return types.Const(val)

@infer
class StaticGetItemArray(AbstractTemplate):
    key = "static_getitem"

    def generic(self, args, kws):
        # Resolution of members for record and structured arrays
        ary, idx = args
        if (isinstance(ary, types.Array) and isinstance(idx, str) and
            isinstance(ary.dtype, types.Record)):
            if idx in ary.dtype.fields:
                return ary.copy(dtype=ary.dtype.typeof(idx), layout='A')


@infer_getattr
class RecordAttribute(AttributeTemplate):
    key = types.Record

    def generic_resolve(self, record, attr):
        ret = record.typeof(attr)
        assert ret
        return ret

@infer
class StaticGetItemRecord(AbstractTemplate):
    key = "static_getitem"

    def generic(self, args, kws):
        # Resolution of members for records
        record, idx = args
        if isinstance(record, types.Record) and isinstance(idx, str):
            ret = record.typeof(idx)
            assert ret
            return ret

@infer
class StaticSetItemRecord(AbstractTemplate):
    key = "static_setitem"

    def generic(self, args, kws):
        # Resolution of members for record and structured arrays
        record, idx, value = args
        if isinstance(record, types.Record) and isinstance(idx, str):
            expectedty = record.typeof(idx)
            if self.context.can_convert(value, expectedty) is not None:
                return signature(types.void, record, types.Const(idx), value)


@infer_getattr
class ArrayCTypesAttribute(AttributeTemplate):
    key = types.ArrayCTypes

    def resolve_data(self, ctinfo):
        return types.uintp


@infer_getattr
class ArrayFlagsAttribute(AttributeTemplate):
    key = types.ArrayFlags

    def resolve_contiguous(self, ctflags):
        return types.boolean

    def resolve_c_contiguous(self, ctflags):
        return types.boolean

    def resolve_f_contiguous(self, ctflags):
        return types.boolean


@infer_getattr
class NestedArrayAttribute(ArrayAttribute):
    key = types.NestedArray


def _expand_integer(ty):
    """
    If *ty* is an integer, expand it to a machine int (like Numpy).
    """
    if isinstance(ty, types.Integer):
        if ty.signed:
            return max(types.intp, ty)
        else:
            return max(types.uintp, ty)
    elif isinstance(ty, types.Boolean):
        return types.intp
    else:
        return ty

def generic_homog(self, args, kws):
    assert not args
    assert not kws
    return signature(self.this.dtype, recvr=self.this)

def generic_expand(self, args, kws):
    assert not args
    assert not kws
    return signature(_expand_integer(self.this.dtype), recvr=self.this)

def sum_expand(self, args, kws):
    """
    sum can be called with or without an axis parameter.
    """
    pysig = None
    if kws:
        def sum_stub(axis):
            pass
        pysig = utils.pysignature(sum_stub)
        # rewrite args
        args = list(args) + [kws['axis']]
        kws = None
    args_len = len(args)
    assert args_len <= 1
    if args_len == 0:
        # No axis parameter so the return type of the summation is a scalar
        # of the type of the array.
        out = signature(_expand_integer(self.this.dtype), *args,
                        recvr=self.this)
    else:
        # There is an axis paramter so the return type of this summation is
        # an array of dimension one less than the input array.
        return_type = types.Array(dtype=_expand_integer(self.this.dtype),
                                  ndim=self.this.ndim-1, layout='C')
        out = signature(return_type, *args, recvr=self.this)
    return out.replace(pysig=pysig)

def generic_expand_cumulative(self, args, kws):
    assert not args
    assert not kws
    assert isinstance(self.this, types.Array)
    return_type = types.Array(dtype=_expand_integer(self.this.dtype),
                              ndim=1, layout='C')
    return signature(return_type, recvr=self.this)

def generic_hetero_real(self, args, kws):
    assert not args
    assert not kws
    if isinstance(self.this.dtype, (types.Integer, types.Boolean)):
        return signature(types.float64, recvr=self.this)
    return signature(self.this.dtype, recvr=self.this)

def generic_hetero_always_real(self, args, kws):
    assert not args
    assert not kws
    if isinstance(self.this.dtype, (types.Integer, types.Boolean)):
        return signature(types.float64, recvr=self.this)
    if isinstance(self.this.dtype, types.Complex):
        return signature(self.this.dtype.underlying_float, recvr=self.this)
    return signature(self.this.dtype, recvr=self.this)

def generic_index(self, args, kws):
    assert not args
    assert not kws
    return signature(types.intp, recvr=self.this)

def install_array_method(name, generic, support_literals=False):
    my_attr = {"key": "array." + name, "generic": generic}
    temp_class = type("Array_" + name, (AbstractTemplate,), my_attr)
    if support_literals:
        temp_class.support_literals = support_literals
    def array_attribute_attachment(self, ary):
        return types.BoundFunction(temp_class, ary)

    setattr(ArrayAttribute, "resolve_" + name, array_attribute_attachment)

# Functions that return the same type as the array
for fname in ["min", "max"]:
    install_array_method(fname, generic_homog)

# Functions that return a machine-width type, to avoid overflows
install_array_method("prod", generic_expand)
install_array_method("sum", sum_expand, support_literals=True)

# Functions that return a machine-width type, to avoid overflows
for fname in ["cumsum", "cumprod"]:
    install_array_method(fname, generic_expand_cumulative)

# Functions that require integer arrays get promoted to float64 return
for fName in ["mean"]:
    install_array_method(fName, generic_hetero_real)

# var and std by definition return in real space and int arrays
# get promoted to float64 return
for fName in ["var", "std"]:
    install_array_method(fName, generic_hetero_always_real)


# Functions that return an index (intp)
install_array_method("argmin", generic_index)
install_array_method("argmax", generic_index)

numba/typing/arraydecl.py
from __future__ import print_function, division, absolute_import

import numpy as np

from collections import namedtuple

from numba import types, utils
from numba.typing.templates import (AttributeTemplate, AbstractTemplate,
                                    infer, infer_getattr, signature,
                                    bound_function)
# import time side effect: array operations requires typing support of sequence
# defined in collections: e.g. array.shape[i]
from numba.typing import collections
from numba.errors import TypingError

Indexing = namedtuple("Indexing", ("index", "result", "advanced"))


def get_array_index_type(ary, idx):
    """
    Returns None or a tuple-3 for the types of the input array, index, and
    resulting type of ``array[index]``.

    Note: This is shared logic for ndarray getitem and setitem.
    """
    if not isinstance(ary, types.Buffer):
        return

    ndim = ary.ndim

    left_indices = []
    right_indices = []
    ellipsis_met = False
    advanced = False
    has_integer = False

    if not isinstance(idx, types.BaseTuple):
        idx = [idx]

    # Walk indices
    for ty in idx:
        if ty is types.ellipsis:
            if ellipsis_met:
                raise TypeError("only one ellipsis allowed in array index "
                                "(got %s)" % (idx,))
            ellipsis_met = True
        elif isinstance(ty, types.SliceType):
            pass
        elif isinstance(ty, types.Integer):
            # Normalize integer index
            ty = types.intp if ty.signed else types.uintp
            # Integer indexing removes the given dimension
            ndim -= 1
            has_integer = True
        elif (isinstance(ty, types.Array) and ty.ndim == 0
              and isinstance(ty.dtype, types.Integer)):
            # 0-d array used as integer index
            ndim -= 1
            has_integer = True
        elif (isinstance(ty, types.Array)
              and ty.ndim == 1
              and isinstance(ty.dtype, (types.Integer, types.Boolean))):
            if advanced or has_integer:
                # We don't support the complicated combination of
                # advanced indices (and integers are considered part
                # of them by Numpy).
                raise NotImplementedError("only one advanced index supported")
            advanced = True
        else:
            raise TypeError("unsupported array index type %s in %s"
                            % (ty, idx))
        (right_indices if ellipsis_met else left_indices).append(ty)

    # Only Numpy arrays support advanced indexing
    if advanced and not isinstance(ary, types.Array):
        return

    # Check indices and result dimensionality
    all_indices = left_indices + right_indices
    if ellipsis_met:
        assert right_indices[0] is types.ellipsis
        del right_indices[0]

    n_indices = len(all_indices) - ellipsis_met
    if n_indices > ary.ndim:
        raise TypeError("cannot index %s with %d indices: %s"
                        % (ary, n_indices, idx))
    if n_indices == ary.ndim and ndim == 0 and not ellipsis_met:
        # Full integer indexing => scalar result
        # (note if ellipsis is present, a 0-d view is returned instead)
        res = ary.dtype

    elif advanced:
        # Result is a copy
        res = ary.copy(ndim=ndim, layout='C', readonly=False)

    else:
        # Result is a view
        if ary.slice_is_copy:
            # Avoid view semantics when the original type creates a copy
            # when slicing.
            return

        # Infer layout
        layout = ary.layout

        def keeps_contiguity(ty, is_innermost):
            # A slice can only keep an array contiguous if it is the
            # innermost index and it is not strided
            return (ty is types.ellipsis or isinstance(ty, types.Integer)
                    or (is_innermost and isinstance(ty, types.SliceType)
                        and not ty.has_step))

        def check_contiguity(outer_indices):
            """
            Whether indexing with the given indices (from outer to inner in
            physical layout order) can keep an array contiguous.
            """
            for ty in outer_indices[:-1]:
                if not keeps_contiguity(ty, False):
                    return False
            if outer_indices and not keeps_contiguity(outer_indices[-1], True):
                return False
            return True

        if layout == 'C':
            # Integer indexing on the left keeps the array C-contiguous
            if n_indices == ary.ndim:
                # If all indices are there, ellipsis's place is indifferent
                left_indices = left_indices + right_indices
                right_indices = []
            if right_indices:
                layout = 'A'
            elif not check_contiguity(left_indices):
                layout = 'A'
        elif layout == 'F':
            # Integer indexing on the right keeps the array F-contiguous
            if n_indices == ary.ndim:
                # If all indices are there, ellipsis's place is indifferent
                right_indices = left_indices + right_indices
                left_indices = []
            if left_indices:
                layout = 'A'
            elif not check_contiguity(right_indices[::-1]):
                layout = 'A'

        if ndim == 0:
            # Implicitly convert to a scalar if the output ndim==0
            res = ary.dtype
        else:
            res = ary.copy(ndim=ndim, layout=layout)

    # Re-wrap indices
    if isinstance(idx, types.BaseTuple):
        idx = types.BaseTuple.from_types(all_indices)
    else:
        idx, = all_indices

    return Indexing(idx, res, advanced)


@infer
class GetItemBuffer(AbstractTemplate):
    key = "getitem"

    def generic(self, args, kws):
        assert not kws
        [ary, idx] = args
        out = get_array_index_type(ary, idx)
        if out is not None:
            return signature(out.result, ary, out.index)

@infer
class SetItemBuffer(AbstractTemplate):
    key = "setitem"

    def generic(self, args, kws):
        assert not kws
        ary, idx, val = args
        if not isinstance(ary, types.Buffer):
            return
        if not ary.mutable:
            raise TypeError("Cannot modify value of type %s" %(ary,))
        out = get_array_index_type(ary, idx)
        if out is None:
            return

        idx = out.index
        res = out.result
        if isinstance(res, types.Array):
            # Indexing produces an array
            if isinstance(val, types.Array):
                if not self.context.can_convert(val.dtype, res.dtype):
                    # DType conversion not possible
                    return
                else:
                    res = val
            elif isinstance(val, types.Sequence):
                if (res.ndim == 1 and
                    self.context.can_convert(val.dtype, res.dtype)):
                    # Allow assignement of sequence to 1d array
                    res = val
                else:
                    # NOTE: sequence-to-array broadcasting is unsupported
                    return
            else:
                # Allow scalar broadcasting
                if self.context.can_convert(val, res.dtype):
                    res = res.dtype
                else:
                    # Incompatible scalar type
                    return
        elif not isinstance(val, types.Array):
            # Single item assignment
            if not self.context.can_convert(val, res):
                # if the array dtype is not yet defined
                if not res.is_precise():
                    # set the array type to use the dtype of value (RHS)
                    newary = ary.copy(dtype=val)
                    return signature(types.none, newary, idx, res)
                else:
                    return
            res = val
        else:
            return
        return signature(types.none, ary, idx, res)


def normalize_shape(shape):
    if isinstance(shape, types.UniTuple):
        if isinstance(shape.dtype, types.Integer):
            dimtype = types.intp if shape.dtype.signed else types.uintp
            return types.UniTuple(dimtype, len(shape))

    elif isinstance(shape, types.Tuple) and shape.count == 0:
        # Force (0 x intp) for consistency with other shapes
        return types.UniTuple(types.intp, 0)


@infer_getattr
class ArrayAttribute(AttributeTemplate):
    key = types.Array

    def resolve_dtype(self, ary):
        return types.DType(ary.dtype)

    def resolve_itemsize(self, ary):
        return types.intp

    def resolve_shape(self, ary):
        return types.UniTuple(types.intp, ary.ndim)

    def resolve_strides(self, ary):
        return types.UniTuple(types.intp, ary.ndim)

    def resolve_ndim(self, ary):
        return types.intp

    def resolve_size(self, ary):
        return types.intp

    def resolve_flat(self, ary):
        return types.NumpyFlatType(ary)

    def resolve_ctypes(self, ary):
        return types.ArrayCTypes(ary)

    def resolve_flags(self, ary):
        return types.ArrayFlags(ary)

    def resolve_T(self, ary):
        if ary.ndim <= 1:
            retty = ary
        else:
            layout = {"C": "F", "F": "C"}.get(ary.layout, "A")
            retty = ary.copy(layout=layout)
        return retty

    def resolve_real(self, ary):
        return self._resolve_real_imag(ary, attr='real')

    def resolve_imag(self, ary):
        return self._resolve_real_imag(ary, attr='imag')

    def _resolve_real_imag(self, ary, attr):
        if ary.dtype in types.complex_domain:
            return ary.copy(dtype=ary.dtype.underlying_float, layout='A')
        elif ary.dtype in types.number_domain:
            res = ary.copy(dtype=ary.dtype)
            if attr == 'imag':
                res = res.copy(readonly=True)
            return res
        else:
            msg = "cannot access .{} of array of {}"
            raise TypingError(msg.format(attr, ary.dtype))

    @bound_function("array.transpose")
    def resolve_transpose(self, ary, args, kws):
        def sentry_shape_scalar(ty):
            if ty in types.number_domain:
                # Guard against non integer type
                if not isinstance(ty, types.Integer):
                    raise TypeError("transpose() arg cannot be {0}".format(ty))
                return True
            else:
                return False

        assert not kws
        if len(args) == 0:
            return signature(self.resolve_T(ary))

        if len(args) == 1:
            shape, = args

            if sentry_shape_scalar(shape):
                assert ary.ndim == 1
                return signature(ary, *args)

            shape = normalize_shape(shape)
            if shape is None:
                return

            assert ary.ndim == shape.count
            return signature(self.resolve_T(ary), shape)

        else:
            if any(not sentry_shape_scalar(a) for a in args):
                raise TypeError("transpose({0}) is not supported".format(
                    ', '.join(args)))
            assert ary.ndim == len(args)
            return signature(self.resolve_T(ary), *args)

    @bound_function("array.copy")
    def resolve_copy(self, ary, args, kws):
        assert not args
        assert not kws
        retty = ary.copy(layout="C", readonly=False)
        return signature(retty)

    @bound_function("array.item")
    def resolve_item(self, ary, args, kws):
        assert not kws
        # We don't support explicit arguments as that's exactly equivalent
        # to regular indexing.  The no-argument form is interesting to
        # allow some degree of genericity when writing functions.
        if not args:
            return signature(ary.dtype)

    @bound_function("array.itemset")
    def resolve_itemset(self, ary, args, kws):
        assert not kws
        # We don't support explicit arguments as that's exactly equivalent
        # to regular indexing.  The no-argument form is interesting to
        # allow some degree of genericity when writing functions.
        if len(args) == 1:
            return signature(types.none, ary.dtype)

    @bound_function("array.nonzero")
    def resolve_nonzero(self, ary, args, kws):
        assert not args
        assert not kws
        # 0-dim arrays return one result array
        ndim = max(ary.ndim, 1)
        retty = types.UniTuple(types.Array(types.intp, 1, 'C'), ndim)
        return signature(retty)

    @bound_function("array.reshape")
    def resolve_reshape(self, ary, args, kws):
        def sentry_shape_scalar(ty):
            if ty in types.number_domain:
                # Guard against non integer type
                if not isinstance(ty, types.Integer):
                    raise TypeError("reshape() arg cannot be {0}".format(ty))
                return True
            else:
                return False

        assert not kws
        if ary.layout not in 'CF':
            # only work for contiguous array
            raise TypeError("reshape() supports contiguous array only")

        if len(args) == 1:
            # single arg
            shape, = args

            if sentry_shape_scalar(shape):
                ndim = 1
            else:
                shape = normalize_shape(shape)
                if shape is None:
                    return
                ndim = shape.count
            retty = ary.copy(ndim=ndim)
            return signature(retty, shape)

        elif len(args) == 0:
            # no arg
            raise TypeError("reshape() take at least one arg")

        else:
            # vararg case
            if any(not sentry_shape_scalar(a) for a in args):
                raise TypeError("reshape({0}) is not supported".format(
                    ', '.join(args)))

            retty = ary.copy(ndim=len(args))
            return signature(retty, *args)

    @bound_function("array.sort")
    def resolve_sort(self, ary, args, kws):
        assert not args
        assert not kws
        if ary.ndim == 1:
            return signature(types.none)

    @bound_function("array.argsort")
    def resolve_argsort(self, ary, args, kws):
        assert not args
        kwargs = dict(kws)
        kind = kwargs.pop('kind', types.Const('quicksort'))
        if kwargs:
            msg = "Unsupported keywords: {!r}"
            raise TypingError(msg.format([k for k in kwargs.keys()]))
        if ary.ndim == 1:
            def argsort_stub(kind='quicksort'):
                pass
            pysig = utils.pysignature(argsort_stub)
            sig = signature(types.Array(types.intp, 1, 'C'), kind).replace(pysig=pysig)
            return sig

    @bound_function("array.view")
    def resolve_view(self, ary, args, kws):
        from .npydecl import _parse_dtype
        assert not kws
        dtype, = args
        dtype = _parse_dtype(dtype)
        if dtype is None:
            return
        retty = ary.copy(dtype=dtype)
        return signature(retty, *args)

    @bound_function("array.astype")
    def resolve_astype(self, ary, args, kws):
        from .npydecl import _parse_dtype
        assert not kws
        dtype, = args
        dtype = _parse_dtype(dtype)
        if dtype is None:
            return
        if not self.context.can_convert(ary.dtype, dtype):
            raise TypeError("astype(%s) not supported on %s: "
                            "cannot convert from %s to %s"
                            % (dtype, ary, ary.dtype, dtype))
        layout = ary.layout if ary.layout in 'CF' else 'C'
        retty = ary.copy(dtype=dtype, layout=layout)
        return signature(retty, *args)

    @bound_function("array.ravel")
    def resolve_ravel(self, ary, args, kws):
        # Only support no argument version (default order='C')
        assert not kws
        assert not args
        return signature(ary.copy(ndim=1, layout='C'))

    @bound_function("array.flatten")
    def resolve_flatten(self, ary, args, kws):
        # Only support no argument version (default order='C')
        assert not kws
        assert not args
        return signature(ary.copy(ndim=1, layout='C'))

    @bound_function("array.take")
    def resolve_take(self, ary, args, kws):
        assert not kws
        argty, = args
        if isinstance(argty, types.Integer):
            sig = signature(ary.dtype, *args)
        elif isinstance(argty, types.Array):
            sig = signature(argty.copy(layout='C', dtype=ary.dtype), *args)
        elif isinstance(argty, types.List): # 1d lists only
            sig = signature(types.Array(ary.dtype, 1, 'C'), *args)
        elif isinstance(argty, types.BaseTuple):
            sig = signature(types.Array(ary.dtype, np.ndim(argty), 'C'), *args)
        else:
            raise TypeError("take(%s) not supported for %s" % argty)
        return sig

    def generic_resolve(self, ary, attr):
        # Resolution of other attributes, for record arrays
        if isinstance(ary.dtype, types.Record):
            if attr in ary.dtype.fields:
                return ary.copy(dtype=ary.dtype.typeof(attr), layout='A')


@infer_getattr
class DTypeAttr(AttributeTemplate):
    key = types.DType

    def resolve_type(self, ary):
        # Wrap the numeric type in NumberClass
        return types.NumberClass(ary.dtype)

    def resolve_kind(self, ary):
        if isinstance(ary.key, types.scalars.Float):
            val = 'f'
        elif isinstance(ary.key, types.scalars.Integer):
            val = 'i'
        else:
            return None  # other types not supported yet
        return types.Const(val)

@infer
class StaticGetItemArray(AbstractTemplate):
    key = "static_getitem"

    def generic(self, args, kws):
        # Resolution of members for record and structured arrays
        ary, idx = args
        if (isinstance(ary, types.Array) and isinstance(idx, str) and
            isinstance(ary.dtype, types.Record)):
            if idx in ary.dtype.fields:
                return ary.copy(dtype=ary.dtype.typeof(idx), layout='A')


@infer_getattr
class RecordAttribute(AttributeTemplate):
    key = types.Record

    def generic_resolve(self, record, attr):
        ret = record.typeof(attr)
        assert ret
        return ret

@infer
class StaticGetItemRecord(AbstractTemplate):
    key = "static_getitem"

    def generic(self, args, kws):
        # Resolution of members for records
        record, idx = args
        if isinstance(record, types.Record) and isinstance(idx, str):
            ret = record.typeof(idx)
            assert ret
            return ret

@infer
class StaticSetItemRecord(AbstractTemplate):
    key = "static_setitem"

    def generic(self, args, kws):
        # Resolution of members for record and structured arrays
        record, idx, value = args
        if isinstance(record, types.Record) and isinstance(idx, str):
            expectedty = record.typeof(idx)
            if self.context.can_convert(value, expectedty) is not None:
                return signature(types.void, record, types.Const(idx), value)


@infer_getattr
class ArrayCTypesAttribute(AttributeTemplate):
    key = types.ArrayCTypes

    def resolve_data(self, ctinfo):
        return types.uintp


@infer_getattr
class ArrayFlagsAttribute(AttributeTemplate):
    key = types.ArrayFlags

    def resolve_contiguous(self, ctflags):
        return types.boolean

    def resolve_c_contiguous(self, ctflags):
        return types.boolean

    def resolve_f_contiguous(self, ctflags):
        return types.boolean


@infer_getattr
class NestedArrayAttribute(ArrayAttribute):
    key = types.NestedArray


def _expand_integer(ty):
    """
    If *ty* is an integer, expand it to a machine int (like Numpy).
    """
    if isinstance(ty, types.Integer):
        if ty.signed:
            return max(types.intp, ty)
        else:
            return max(types.uintp, ty)
    elif isinstance(ty, types.Boolean):
        return types.intp
    else:
        return ty

def generic_homog(self, args, kws):
    assert not args
    assert not kws
    return signature(self.this.dtype, recvr=self.this)

def generic_expand(self, args, kws):
    assert not args
    assert not kws
    return signature(_expand_integer(self.this.dtype), recvr=self.this)

def sum_expand(self, args, kws):
    """
    sum can be called with or without an axis parameter.
    """
    pysig = None
    if kws:
        def sum_stub(axis):
            pass
        pysig = utils.pysignature(sum_stub)
        # rewrite args
        args = list(args) + [kws['axis']]
        kws = None
    args_len = len(args)
    assert args_len <= 1
    if args_len == 0:
        # No axis parameter so the return type of the summation is a scalar
        # of the type of the array.
        out = signature(_expand_integer(self.this.dtype), *args,
                        recvr=self.this)
    else:
        # There is an axis paramter so the return type of this summation is
        # an array of dimension one less than the input array.
        return_type = types.Array(dtype=_expand_integer(self.this.dtype),
                                  ndim=self.this.ndim-1, layout='C')
        out = signature(return_type, *args, recvr=self.this)
    return out.replace(pysig=pysig)

def generic_expand_cumulative(self, args, kws):
    assert not args
    assert not kws
    assert isinstance(self.this, types.Array)
    return_type = types.Array(dtype=_expand_integer(self.this.dtype),
                              ndim=1, layout='C')
    return signature(return_type, recvr=self.this)

def generic_hetero_real(self, args, kws):
    assert not args
    assert not kws
    if isinstance(self.this.dtype, (types.Integer, types.Boolean)):
        return signature(types.float64, recvr=self.this)
    return signature(self.this.dtype, recvr=self.this)

def generic_hetero_always_real(self, args, kws):
    assert not args
    assert not kws
    if isinstance(self.this.dtype, (types.Integer, types.Boolean)):
        return signature(types.float64, recvr=self.this)
    if isinstance(self.this.dtype, types.Complex):
        return signature(self.this.dtype.underlying_float, recvr=self.this)
    return signature(self.this.dtype, recvr=self.this)

def generic_index(self, args, kws):
    assert not args
    assert not kws
    return signature(types.intp, recvr=self.this)

def install_array_method(name, generic, support_literals=False):
    my_attr = {"key": "array." + name, "generic": generic}
    temp_class = type("Array_" + name, (AbstractTemplate,), my_attr)
    if support_literals:
        temp_class.support_literals = support_literals
    def array_attribute_attachment(self, ary):
        return types.BoundFunction(temp_class, ary)

    setattr(ArrayAttribute, "resolve_" + name, array_attribute_attachment)

# Functions that return the same type as the array
for fname in ["min", "max"]:
    install_array_method(fname, generic_homog)

# Functions that return a machine-width type, to avoid overflows
install_array_method("prod", generic_expand)
install_array_method("sum", sum_expand, support_literals=True)

# Functions that return a machine-width type, to avoid overflows
for fname in ["cumsum", "cumprod"]:
    install_array_method(fname, generic_expand_cumulative)

# Functions that require integer arrays get promoted to float64 return
for fName in ["mean"]:
    install_array_method(fName, generic_hetero_real)

# var and std by definition return in real space and int arrays
# get promoted to float64 return
for fName in ["var", "std"]:
    install_array_method(fName, generic_hetero_always_real)


# Functions that return an index (intp)
install_array_method("argmin", generic_index)
install_array_method("argmax", generic_index)

numba/typing/arraydecl.py
diff --git a/numba/tests/test_fancy_indexing.py b/numba/tests/test_fancy_indexing.py
index fd8be585bde..efa037ba5ac 100644
--- a/numba/tests/test_fancy_indexing.py
+++ b/numba/tests/test_fancy_indexing.py
@@ -117,6 +117,16 @@ def test_getitem_tuple_and_ellipsis(self):
 
         self.check_getitem_indices(arr, indices)
 
+    def test_ellipsis_getsetitem(self):
+        # See https://github.com/numba/numba/issues/3225
+        @jit(nopython=True)
+        def foo(arr, v):
+            arr[..., 0] = arr[..., 1]
+
+        arr = np.arange(2)
+        foo(arr, 1)
+        self.assertEqual(arr[0], arr[1])
+
     @tag('important')
     def test_getitem_array(self):
         # Test advanced indexing with a single array index
@@ -187,7 +197,7 @@ def check(arr, ind):
         # 3. nd array index
         # 4. reflected list
         # 5. tuples
-        
+
         test_indices = []
         test_indices.append(1)
         test_indices.append(np.array([1, 5, 1, 11, 3]))
@@ -195,7 +205,7 @@ def check(arr, ind):
         test_indices.append([1, 5, 1, 11, 3])
         test_indices.append((1, 5, 1))
         test_indices.append(((1, 5, 1), (11, 3, 2)))
-    
+
         for dt in [np.int64, np.complex128]:
             A = np.arange(12, dtype=dt).reshape((4, 3))
             for ind in test_indices:
@@ -212,7 +222,7 @@ def check(arr, ind):
         # check float indexing raises
         with self.assertRaises(TypingError):
             cfunc(A, [1.7])
-       
+
         # check unsupported arg raises
         with self.assertRaises(TypingError):
             take_kws = jit(nopython=True)(np_take_kws)
diff --git a/numba/tests/test_indexing.py b/numba/tests/test_indexing.py
index 3b075bbd6f9..813d07e5aa1 100644
--- a/numba/tests/test_indexing.py
+++ b/numba/tests/test_indexing.py
@@ -679,7 +679,7 @@ def run(a):
             cfunc = compile_func(a)
             for i, j in itertools.product(bounds, bounds):
                 x = cfunc(a, i, j)
-                self.assertPreciseEqual(pyfunc(a, i, j), cfunc(a, i, j))
+                np.testing.assert_equal(pyfunc(a, i, j), cfunc(a, i, j))
 
         run(np.arange(16, dtype='i4').reshape(4, 4))
         run(np.arange(27, dtype='i4').reshape(3, 3, 3))
diff --git a/numba/typing/arraydecl.py b/numba/typing/arraydecl.py
index 6119200fe0c..ba3cd2ec6d9 100644
--- a/numba/typing/arraydecl.py
+++ b/numba/typing/arraydecl.py
@@ -144,7 +144,11 @@ def check_contiguity(outer_indices):
             elif not check_contiguity(right_indices[::-1]):
                 layout = 'A'
 
-        res = ary.copy(ndim=ndim, layout=layout)
+        if ndim == 0:
+            # Implicitly convert to a scalar if the output ndim==0
+            res = ary.dtype
+        else:
+            res = ary.copy(ndim=ndim, layout=layout)
 
     # Re-wrap indices
     if isinstance(idx, types.BaseTuple):

Ellipsis indexing and assignment not working when needed number of `:` is zero
I was trying to index using `...` on both sides of assignment so I could use the same function for inputs with 1, 2, or N dimensions.

Here is code for the MCVE (using `numba` 0.39.0):
```python
import numpy as np
import numba

def func(A, B, indices):
    rv = A.copy()
    for i in range(indices.size):
        index = indices[i]
        rv[..., index] = B[..., index]
    return rv

jitted = numba.njit(func)

A = np.ones((3, 5))
B = 2 * np.ones((3, 5))
indices = np.array([0, 2])

jitted(A[0], B[0], indices)  # <-- raises.  traceback below
```
Let's compare results using `numpy` and `numba`:
```python
In [2]: func(A, B, indices)
Out[2]: 
array([[2., 1., 2., 1., 1.],
       [2., 1., 2., 1., 1.],
       [2., 1., 2., 1., 1.]])

In [3]: func(A[0], B[0], indices)
Out[3]: array([2., 1., 2., 1., 1.])

In [4]: jitted(A, B, indices)
Out[4]: 
array([[2., 1., 2., 1., 1.],
       [2., 1., 2., 1., 1.],
       [2., 1., 2., 1., 1.]])

In [5]: jitted(A[0], B[0], indices)  # <-- raises.  traceback below
```
Traceback:
<details>

```python-traceback
---------------------------------------------------------------------------
NotImplementedError                       Traceback (most recent call last)
~/miniconda3/envs/numba3/lib/python3.7/site-packages/numba/targets/base.py in cast(self, builder, val, fromty, toty)
    674         try:
--> 675             impl = self._casts.find((fromty, toty))
    676             return impl(self, builder, fromty, toty, val)

~/miniconda3/envs/numba3/lib/python3.7/site-packages/numba/targets/base.py in find(self, sig)
     47         if out is None:
---> 48             out = self._find(sig)
     49             self._cache[sig] = out

~/miniconda3/envs/numba3/lib/python3.7/site-packages/numba/targets/base.py in _find(self, sig)
     56         else:
---> 57             raise NotImplementedError(self, sig)
     58 

NotImplementedError: (<numba.targets.base.OverloadSelector object at 0x1059269e8>, (array(float64, 0d, C), float64))

During handling of the above exception, another exception occurred:

NotImplementedError                       Traceback (most recent call last)
~/miniconda3/envs/numba3/lib/python3.7/site-packages/numba/errors.py in new_error_context(fmt_, *args, **kwargs)
    576     try:
--> 577         yield
    578     except NumbaError as e:

~/miniconda3/envs/numba3/lib/python3.7/site-packages/numba/lowering.py in lower_block(self, block)
    253                                    loc=self.loc, errcls_=defaulterrcls):
--> 254                 self.lower_inst(inst)
    255 

~/miniconda3/envs/numba3/lib/python3.7/site-packages/numba/lowering.py in lower_inst(self, inst)
    357             assert signature is not None
--> 358             return self.lower_setitem(inst.target, inst.index, inst.value, signature)
    359 

~/miniconda3/envs/numba3/lib/python3.7/site-packages/numba/lowering.py in lower_setitem(self, target_var, index_var, value_var, signature)
    429 
--> 430         return impl(self.builder, (target, index, value))
    431 

~/miniconda3/envs/numba3/lib/python3.7/site-packages/numba/targets/base.py in __call__(self, builder, args)
   1078     def __call__(self, builder, args):
-> 1079         return self._imp(self._context, builder, self._sig, args)
   1080 

~/miniconda3/envs/numba3/lib/python3.7/site-packages/numba/targets/arrayobj.py in setitem_array(context, builder, sig, args)
    481     # Store source value the given location
--> 482     val = context.cast(builder, val, valty, aryty.dtype)
    483     store_item(context, builder, aryty, val, dataptr)

~/miniconda3/envs/numba3/lib/python3.7/site-packages/numba/targets/base.py in cast(self, builder, val, fromty, toty)
    678             raise NotImplementedError(
--> 679                 "Cannot cast %s to %s: %s" % (fromty, toty, val))
    680 

NotImplementedError: Cannot cast array(float64, 0d, C) to float64: %".417" = load {i8*, i8*, i64, i64, double*, [0 x i64], [0 x i64]}, {i8*, i8*, i64, i64, double*, [0 x i64], [0 x i64]}* %"$22.9"

During handling of the above exception, another exception occurred:

LoweringError                             Traceback (most recent call last)
<ipython-input-6-e6ce0775290a> in <module>()
----> 1 jitted(A[0], B[0], indices)

~/miniconda3/envs/numba3/lib/python3.7/site-packages/numba/dispatcher.py in _compile_for_args(self, *args, **kws)
    366                     e.patch_message(''.join(e.args) + help_msg)
    367             # ignore the FULL_TRACEBACKS config, this needs reporting!
--> 368             raise e
    369 
    370     def inspect_llvm(self, signature=None):

~/miniconda3/envs/numba3/lib/python3.7/site-packages/numba/dispatcher.py in _compile_for_args(self, *args, **kws)
    323                 argtypes.append(self.typeof_pyval(a))
    324         try:
--> 325             return self.compile(tuple(argtypes))
    326         except errors.TypingError as e:
    327             # Intercept typing error that may be due to an argument

~/miniconda3/envs/numba3/lib/python3.7/site-packages/numba/dispatcher.py in compile(self, sig)
    651 
    652                 self._cache_misses[sig] += 1
--> 653                 cres = self._compiler.compile(args, return_type)
    654                 self.add_overload(cres)
    655                 self._cache.save_overload(sig, cres)

~/miniconda3/envs/numba3/lib/python3.7/site-packages/numba/dispatcher.py in compile(self, args, return_type)
     81                                       args=args, return_type=return_type,
     82                                       flags=flags, locals=self.locals,
---> 83                                       pipeline_class=self.pipeline_class)
     84         # Check typing error if object mode is used
     85         if cres.typing_error is not None and not flags.enable_pyobject:

~/miniconda3/envs/numba3/lib/python3.7/site-packages/numba/compiler.py in compile_extra(typingctx, targetctx, func, args, return_type, flags, locals, library, pipeline_class)
    871     pipeline = pipeline_class(typingctx, targetctx, library,
    872                               args, return_type, flags, locals)
--> 873     return pipeline.compile_extra(func)
    874 
    875 

~/miniconda3/envs/numba3/lib/python3.7/site-packages/numba/compiler.py in compile_extra(self, func)
    365         self.lifted = ()
    366         self.lifted_from = None
--> 367         return self._compile_bytecode()
    368 
    369     def compile_ir(self, func_ir, lifted=(), lifted_from=None):

~/miniconda3/envs/numba3/lib/python3.7/site-packages/numba/compiler.py in _compile_bytecode(self)
    802         """
    803         assert self.func_ir is None
--> 804         return self._compile_core()
    805 
    806     def _compile_ir(self):

~/miniconda3/envs/numba3/lib/python3.7/site-packages/numba/compiler.py in _compile_core(self)
    789         self.define_pipelines(pm)
    790         pm.finalize()
--> 791         res = pm.run(self.status)
    792         if res is not None:
    793             # Early pipeline completion

~/miniconda3/envs/numba3/lib/python3.7/site-packages/numba/compiler.py in run(self, status)
    251                     # No more fallback pipelines?
    252                     if is_final_pipeline:
--> 253                         raise patched_exception
    254                     # Go to next fallback pipeline
    255                     else:

~/miniconda3/envs/numba3/lib/python3.7/site-packages/numba/compiler.py in run(self, status)
    243                 try:
    244                     event(stage_name)
--> 245                     stage()
    246                 except _EarlyPipelineCompletion as e:
    247                     return e.result

~/miniconda3/envs/numba3/lib/python3.7/site-packages/numba/compiler.py in stage_nopython_backend(self)
    676         """
    677         lowerfn = self.backend_nopython_mode
--> 678         self._backend(lowerfn, objectmode=False)
    679 
    680     def stage_compile_interp_mode(self):

~/miniconda3/envs/numba3/lib/python3.7/site-packages/numba/compiler.py in _backend(self, lowerfn, objectmode)
    626             self.library.enable_object_caching()
    627 
--> 628         lowered = lowerfn()
    629         signature = typing.signature(self.return_type, *self.args)
    630         self.cr = compile_result(

~/miniconda3/envs/numba3/lib/python3.7/site-packages/numba/compiler.py in backend_nopython_mode(self)
    613                 self.return_type,
    614                 self.calltypes,
--> 615                 self.flags)
    616 
    617     def _backend(self, lowerfn, objectmode):

~/miniconda3/envs/numba3/lib/python3.7/site-packages/numba/compiler.py in native_lowering_stage(targetctx, library, interp, typemap, restype, calltypes, flags)
    990 
    991     lower = lowering.Lower(targetctx, library, fndesc, interp)
--> 992     lower.lower()
    993     if not flags.no_cpython_wrapper:
    994         lower.create_cpython_wrapper(flags.release_gil)

~/miniconda3/envs/numba3/lib/python3.7/site-packages/numba/lowering.py in lower(self)
    171         if self.generator_info is None:
    172             self.genlower = None
--> 173             self.lower_normal_function(self.fndesc)
    174         else:
    175             self.genlower = self.GeneratorLower(self)

~/miniconda3/envs/numba3/lib/python3.7/site-packages/numba/lowering.py in lower_normal_function(self, fndesc)
    212         # Init argument values
    213         self.extract_function_arguments()
--> 214         entry_block_tail = self.lower_function_body()
    215 
    216         # Close tail of entry block

~/miniconda3/envs/numba3/lib/python3.7/site-packages/numba/lowering.py in lower_function_body(self)
    237             bb = self.blkmap[offset]
    238             self.builder.position_at_end(bb)
--> 239             self.lower_block(block)
    240 
    241         self.post_lower()

~/miniconda3/envs/numba3/lib/python3.7/site-packages/numba/lowering.py in lower_block(self, block)
    252             with new_error_context('lowering "{inst}" at {loc}', inst=inst,
    253                                    loc=self.loc, errcls_=defaulterrcls):
--> 254                 self.lower_inst(inst)
    255 
    256     def create_cpython_wrapper(self, release_gil=False):

~/miniconda3/envs/numba3/lib/python3.7/contextlib.py in __exit__(self, type, value, traceback)
    128                 value = type()
    129             try:
--> 130                 self.gen.throw(type, value, traceback)
    131             except StopIteration as exc:
    132                 # Suppress StopIteration *unless* it's the same exception that

~/miniconda3/envs/numba3/lib/python3.7/site-packages/numba/errors.py in new_error_context(fmt_, *args, **kwargs)
    583         from numba import config
    584         tb = sys.exc_info()[2] if config.FULL_TRACEBACKS else None
--> 585         six.reraise(type(newerr), newerr, tb)
    586 
    587 

~/miniconda3/envs/numba3/lib/python3.7/site-packages/numba/six.py in reraise(tp, value, tb)
    657         if value.__traceback__ is not tb:
    658             raise value.with_traceback(tb)
--> 659         raise value
    660 
    661 else:

LoweringError: Failed at nopython (nopython mode backend)
Cannot cast array(float64, 0d, C) to float64: %".417" = load {i8*, i8*, i64, i64, double*, [0 x i64], [0 x i64]}, {i8*, i8*, i64, i64, double*, [0 x i64], [0 x i64]}* %"$22.9"

File "<ipython-input-1-f6cc8d5fb861>", line 8:
def func(A, B, indices):
    <source elided>
        index = indices[i]
        rv[..., index] = B[..., index]
        ^
[1] During: lowering "rv[$22.13] = $22.9" at <ipython-input-1-f6cc8d5fb861> (8)
-------------------------------------------------------------------------------
This should not have happened, a problem has occurred in Numba's internals.

Please report the error message and traceback, along with a minimal reproducer
at: https://github.com/numba/numba/issues/new

If more help is needed please feel free to speak to the Numba core developers
directly at: https://gitter.im/numba/numba

Thanks in advance for your help in improving Numba!
```

</details>
