# BSD 3-Clause License; see https://github.com/scikit-hep/awkward-1.0/blob/master/LICENSE

from __future__ import absolute_import

import json
import ctypes

import numpy
import numba

import awkward1.layout
import awkward1._util
import awkward1._connect._numba.arrayview

@numba.extending.typeof_impl.register(awkward1.layout.NumpyArray)
def typeof_NumpyArray(obj, c):
    t = numba.typeof(numpy.asarray(obj))
    return NumpyArrayType(numba.types.Array(t.dtype, t.ndim, "A"),
                          numba.typeof(obj.identities),
                          obj.parameters)

@numba.extending.typeof_impl.register(awkward1.layout.RegularArray)
def typeof_RegularArray(obj, c):
    return RegularArrayType(numba.typeof(obj.content),
                            obj.size,
                            numba.typeof(obj.identities),
                            obj.parameters)

@numba.extending.typeof_impl.register(awkward1.layout.ListArray32)
@numba.extending.typeof_impl.register(awkward1.layout.ListArrayU32)
@numba.extending.typeof_impl.register(awkward1.layout.ListArray64)
@numba.extending.typeof_impl.register(awkward1.layout.ListOffsetArray32)
@numba.extending.typeof_impl.register(awkward1.layout.ListOffsetArrayU32)
@numba.extending.typeof_impl.register(awkward1.layout.ListOffsetArray64)
def typeof_ListArray(obj, c):
    return ListArrayType(numba.typeof(numpy.asarray(obj.starts)),
                         numba.typeof(obj.content),
                         numba.typeof(obj.identities),
                         obj.parameters)

@numba.extending.typeof_impl.register(awkward1.layout.IndexedArray32)
@numba.extending.typeof_impl.register(awkward1.layout.IndexedArrayU32)
@numba.extending.typeof_impl.register(awkward1.layout.IndexedArray64)
def typeof_IndexedArray(obj, c):
    return IndexedArrayType(numba.typeof(numpy.asarray(obj.index)),
                            numba.typeof(obj.content),
                            numba.typeof(obj.identities),
                            obj.parameters)

@numba.extending.typeof_impl.register(awkward1.layout.IndexedOptionArray32)
@numba.extending.typeof_impl.register(awkward1.layout.IndexedOptionArray64)
def typeof_IndexedOptionArray(obj, c):
    return IndexedOptionArrayType(numba.typeof(numpy.asarray(obj.index)),
                                  numba.typeof(obj.content),
                                  numba.typeof(obj.identities),
                                  obj.parameters)

@numba.extending.typeof_impl.register(awkward1.layout.ByteMaskedArray)
def typeof_ByteMaskedArray(obj, c):
    return ByteMaskedArrayType(numba.typeof(numpy.asarray(obj.mask)),
                               numba.typeof(obj.content),
                               obj.valid_when,
                               numba.typeof(obj.identities),
                               obj.parameters)

@numba.extending.typeof_impl.register(awkward1.layout.BitMaskedArray)
def typeof_BitMaskedArray(obj, c):
    return BitMaskedArrayType(numba.typeof(numpy.asarray(obj.mask)),
                              numba.typeof(obj.content),
                              obj.valid_when,
                              obj.lsb_order,
                              numba.typeof(obj.identities),
                              obj.parameters)

@numba.extending.typeof_impl.register(awkward1.layout.UnmaskedArray)
def typeof_UnmaskedArray(obj, c):
    return UnmaskedArrayType(numba.typeof(obj.content),
                             numba.typeof(obj.identities),
                             obj.parameters)

@numba.extending.typeof_impl.register(awkward1.layout.RecordArray)
def typeof_RecordArray(obj, c):
    return RecordArrayType(tuple(numba.typeof(x) for x in obj.contents),
                           obj.recordlookup,
                           numba.typeof(obj.identities),
                           obj.parameters)

@numba.extending.typeof_impl.register(awkward1.layout.Record)
def typeof_Record(obj, c):
    return RecordType(numba.typeof(obj.array))

@numba.extending.typeof_impl.register(awkward1.layout.UnionArray8_32)
@numba.extending.typeof_impl.register(awkward1.layout.UnionArray8_U32)
@numba.extending.typeof_impl.register(awkward1.layout.UnionArray8_64)
def typeof_UnionArray(obj, c):
    return UnionArrayType(numba.typeof(numpy.asarray(obj.tags)),
                          numba.typeof(numpy.asarray(obj.index)),
                          tuple(numba.typeof(x) for x in obj.contents),
                          numba.typeof(obj.identities), obj.parameters)

@numba.extending.typeof_impl.register(awkward1.layout.VirtualArray)
def typeof_VirtualArray(obj, c):
    if obj.form.form is None:
        raise ValueError(
            "VirtualArrays without a known 'form' can't be used in Numba")
    if obj.form.has_identities:
        raise NotImplementedError("TODO: identities in VirtualArray")
    return VirtualArrayType(obj.form.form, numba.none, obj.parameters)

class ContentType(numba.types.Type):
    @classmethod
    def tolookup_identities(cls, layout, positions, sharedptrs, arrays):
        if layout.identities is None:
            positions.append(-1)
            sharedptrs.append(None)
        else:
            arrays.append(numpy.asarray(layout.identities))
            positions.append(arrays[-1])
            sharedptrs.append(None)

    @classmethod
    def form_tolookup_identities(cls, form, positions, sharedptrs, arrays):
        if not form.has_identities:
            positions.append(-1)
            sharedptrs.append(None)
        else:
            arrays.append(None)
            positions.append(0)
            sharedptrs.append(None)

    @classmethod
    def from_form_identities(cls, form):
        if not form.has_identities:
            return numba.none
        else:
            raise NotImplementedError("TODO: identities in VirtualArray")

    @classmethod
    def from_form_index(cls, index_string):
        if index_string == "i8":
            return numba.types.Array(numba.int8, 1, "C")
        elif index_string == "u8":
            return numba.types.Array(numba.uint8, 1, "C")
        elif index_string == "i32":
            return numba.types.Array(numba.int32, 1, "C")
        elif index_string == "u32":
            return numba.types.Array(numba.uint32, 1, "C")
        elif index_string == "i64":
            return numba.types.Array(numba.int64, 1, "C")
        else:
            raise AssertionError(
                "unrecognized Form index type: {0}".format(index_string))

    def form_fill_identities(self, pos, layout, lookup):
        if layout.identities is not None:
            lookup.arrayptr[pos + self.IDENTITIES] = \
                numpy.asarray(layout.identities).ctypes.data

    def IndexOf(self, arraytype):
        if arraytype.dtype.bitwidth == 8 and arraytype.dtype.signed:
            return awkward1.layout.Index8
        elif arraytype.dtype.bitwidth == 8:
            return awkward1.layout.IndexU8
        elif arraytype.dtype.bitwidth == 32 and arraytype.dtype.signed:
            return awkward1.layout.Index32
        elif arraytype.dtype.bitwidth == 32:
            return awkward1.layout.IndexU32
        elif arraytype.dtype.bitwidth == 64 and arraytype.dtype.signed:
            return awkward1.layout.Index64
        else:
            raise AssertionError(
                    "no Index* type for array: {0}".format(arraytype))

    def getitem_at_check(self, viewtype):
        typer = awkward1._util.numba_array_typer(viewtype.type,
                                                 viewtype.behavior)
        if typer is None:
            return self.getitem_at(viewtype)
        else:
            return typer(viewtype)

    def getitem_range(self, viewtype):
        return awkward1._connect._numba.arrayview.wrap(self, viewtype, None)

    def getitem_field(self, viewtype, key):
        if self.hasfield(key):
            return awkward1._connect._numba.arrayview.wrap(
                     self, viewtype, viewtype.fields + (key,))
        else:
            raise TypeError(
                    "array does not have a field with key {0}".format(
                                                                  repr(key)))

    def lower_getitem_at_check(self,
                               context,
                               builder,
                               rettype,
                               viewtype,
                               viewval,
                               viewproxy,
                               attype,
                               atval,
                               wrapneg,
                               checkbounds):
        lower = awkward1._util.numba_array_lower(viewtype.type,
                                                 viewtype.behavior)
        if lower is not None:
            atval = regularize_atval(context,
                                     builder,
                                     viewproxy,
                                     attype,
                                     atval,
                                     wrapneg,
                                     checkbounds)
            return lower(context,
                         builder,
                         rettype,
                         viewtype,
                         viewval,
                         viewproxy,
                         attype,
                         atval)
        else:
            return self.lower_getitem_at(context,
                                         builder,
                                         rettype,
                                         viewtype,
                                         viewval,
                                         viewproxy,
                                         attype,
                                         atval,
                                         wrapneg,
                                         checkbounds)

    def lower_getitem_range(self,
                            context,
                            builder,
                            rettype,
                            viewtype,
                            viewval,
                            viewproxy,
                            start,
                            stop,
                            wrapneg):
        length = builder.sub(viewproxy.stop, viewproxy.start)

        regular_start = numba.core.cgutils.alloca_once_value(builder, start)
        regular_stop = numba.core.cgutils.alloca_once_value(builder, stop)

        if wrapneg:
            with builder.if_then(builder.icmp_signed(
                   "<", start, context.get_constant(numba.intp, 0))):
                builder.store(builder.add(start, length), regular_start)
            with builder.if_then(builder.icmp_signed(
                   "<", stop, context.get_constant(numba.intp, 0))):
                builder.store(builder.add(stop, length), regular_stop)

        with builder.if_then(builder.icmp_signed(
               "<", builder.load(regular_start),
                    context.get_constant(numba.intp, 0))):
            builder.store(context.get_constant(numba.intp, 0), regular_start)
        with builder.if_then(builder.icmp_signed(
               ">", builder.load(regular_start), length)):
            builder.store(length, regular_start)

        with builder.if_then(builder.icmp_signed(
               "<", builder.load(regular_stop), builder.load(regular_start))):
            builder.store(builder.load(regular_start), regular_stop)
        with builder.if_then(builder.icmp_signed(
               ">", builder.load(regular_stop), length)):
            builder.store(length, regular_stop)

        proxyout = context.make_helper(builder, rettype)
        proxyout.pos        = viewproxy.pos
        proxyout.start      = builder.add(viewproxy.start,
                                          builder.load(regular_start))
        proxyout.stop       = builder.add(viewproxy.start,
                                          builder.load(regular_stop))
        proxyout.arrayptrs  = viewproxy.arrayptrs
        proxyout.sharedptrs = viewproxy.sharedptrs
        proxyout.pylookup   = viewproxy.pylookup
        return proxyout._getvalue()

    def lower_getitem_field(self, context, builder, viewtype, viewval, key):
        return viewval

def posat(context, builder, pos, offset):
    return builder.add(pos, context.get_constant(numba.intp, offset))

def getat(context, builder, baseptr, offset, rettype=None):
    ptrtype = None
    if rettype is not None:
        ptrtype = context.get_value_type(numba.types.CPointer(rettype))
        bitwidth = rettype.bitwidth
    else:
        bitwidth = numba.intp.bitwidth
    byteoffset = builder.mul(offset,
                             context.get_constant(numba.intp, bitwidth // 8))
    return builder.load(numba.core.cgutils.pointer_add(builder,
                                                       baseptr,
                                                       byteoffset,
                                                       ptrtype))

def regularize_atval(context,
                     builder,
                     viewproxy,
                     attype,
                     atval,
                     wrapneg,
                     checkbounds):
    atval = awkward1._connect._numba.castint(context,
                                             builder,
                                             attype,
                                             numba.intp,
                                             atval)

    if not attype.signed:
        wrapneg = False

    if wrapneg or checkbounds:
        length = builder.sub(viewproxy.stop, viewproxy.start)

        if wrapneg:
            regular_atval = numba.core.cgutils.alloca_once_value(builder,
                                                                 atval)
            with builder.if_then(builder.icmp_signed(
                   "<", atval, context.get_constant(numba.intp, 0))):
                builder.store(builder.add(atval, length), regular_atval)
            atval = builder.load(regular_atval)

        if checkbounds:
            with builder.if_then(
                   builder.or_(
                     builder.icmp_signed(
                       "<", atval, context.get_constant(numba.intp, 0)),
                     builder.icmp_signed(">=", atval, length))):
                context.call_conv.return_user_exc(
                    builder, ValueError, ("slice index out of bounds",))

    return awkward1._connect._numba.castint(context,
                                            builder,
                                            atval.type,
                                            numba.intp,
                                            atval)

class NumpyArrayType(ContentType):
    IDENTITIES = 0
    ARRAY = 1

    @classmethod
    def tolookup(cls, layout, positions, sharedptrs, arrays):
        array = numpy.asarray(layout)
        assert len(array.shape) == 1
        pos = len(positions)
        cls.tolookup_identities(layout, positions, sharedptrs, arrays)
        sharedptrs[-1] = layout._persistent_shared_ptr
        positions.append(array)
        sharedptrs.append(None)
        arrays.append(array)
        return pos

    @classmethod
    def form_tolookup(cls, form, positions, sharedptrs, arrays):
        if len(form.inner_shape) != 0:
            raise NotImplementedError(
                "NumpyForm is multidimensional; TODO: convert to RegularForm,"
                " just as NumpyArrays are converted to RegularArrays")
        pos = len(positions)
        cls.form_tolookup_identities(form, positions, sharedptrs, arrays)
        sharedptrs[-1] = 0
        positions.append(0)
        sharedptrs.append(None)
        arrays.append(0)
        return pos

    @classmethod
    def from_form(cls, form):
        if len(form.inner_shape) != 0:
            raise NotImplementedError(
                "NumpyForm is multidimensional; TODO: convert to RegularForm,"
                " just as NumpyArrays are converted to RegularArrays")
        if form.primitive == "float64":
            arraytype = numba.types.Array(numba.float64, 1, "A")
        elif form.primitive == "float32":
            arraytype = numba.types.Array(numba.float32, 1, "A")
        elif form.primitive == "int64":
            arraytype = numba.types.Array(numba.int64, 1, "A")
        elif form.primitive == "uint64":
            arraytype = numba.types.Array(numba.uint64, 1, "A")
        elif form.primitive == "int32":
            arraytype = numba.types.Array(numba.int32, 1, "A")
        elif form.primitive == "uint32":
            arraytype = numba.types.Array(numba.uint32, 1, "A")
        elif form.primitive == "int16":
            arraytype = numba.types.Array(numba.int16, 1, "A")
        elif form.primitive == "uint16":
            arraytype = numba.types.Array(numba.uint16, 1, "A")
        elif form.primitive == "int8":
            arraytype = numba.types.Array(numba.int8, 1, "A")
        elif form.primitive == "uint8":
            arraytype = numba.types.Array(numba.uint8, 1, "A")
        elif form.primitive == "bool":
            arraytype = numba.types.Array(numba.bool, 1, "A")
        else:
            raise ValueError("unrecognized NumpyForm.primitive type: {0}"
                             .format(form.primitive))
        return NumpyArrayType(arraytype,
                              cls.from_form_identities(form),
                              form.parameters)

    def __init__(self, arraytype, identitiestype, parameters):
        super(NumpyArrayType, self).__init__(
            name="awkward1.NumpyArrayType({0}, {1}, {2})".format(
              arraytype.name, identitiestype.name, json.dumps(parameters)))
        self.arraytype = arraytype
        self.identitiestype = identitiestype
        self.parameters = parameters

    def form_fill(self, pos, layout, lookup):
        lookup.sharedptrs_hold[pos] = layout._persistent_shared_ptr
        lookup.sharedptrs[pos] = lookup.sharedptrs_hold[pos].ptr()
        self.form_fill_identities(pos, layout, lookup)

        lookup.original_positions[pos + self.ARRAY] = numpy.asarray(layout)
        lookup.arrayptrs[pos + self.ARRAY] = \
                      lookup.original_positions[pos + self.ARRAY].ctypes.data

    def tolayout(self, lookup, pos, fields):
        assert fields == ()
        return awkward1.layout.NumpyArray(
                 lookup.original_positions[pos + self.ARRAY],
                 parameters=self.parameters)

    def hasfield(self, key):
        return False

    def getitem_at(self, viewtype):
        return self.arraytype.dtype

    def lower_getitem_at(self,
                         context,
                         builder,
                         rettype,
                         viewtype,
                         viewval,
                         viewproxy,
                         attype,
                         atval,
                         wrapneg,
                         checkbounds):
        whichpos = posat(context, builder, viewproxy.pos, self.ARRAY)
        arrayptr = getat(context, builder, viewproxy.arrayptrs, whichpos)
        atval = regularize_atval(context,
                                 builder,
                                 viewproxy,
                                 attype,
                                 atval,
                                 wrapneg,
                                 checkbounds)
        arraypos = builder.add(viewproxy.start, atval)
        return getat(context, builder, arrayptr, arraypos, rettype)

class RegularArrayType(ContentType):
    IDENTITIES = 0
    CONTENT = 1

    @classmethod
    def tolookup(cls, layout, positions, sharedptrs, arrays):
        pos = len(positions)
        cls.tolookup_identities(layout, positions, sharedptrs, arrays)
        sharedptrs[-1] = layout._persistent_shared_ptr
        positions.append(None)
        sharedptrs.append(None)
        positions[pos + cls.CONTENT] = \
          awkward1._connect._numba.arrayview.tolookup(layout.content,
                                                      positions,
                                                      sharedptrs,
                                                      arrays)
        return pos

    @classmethod
    def form_tolookup(cls, form, positions, sharedptrs, arrays):
        pos = len(positions)
        cls.form_tolookup_identities(form, positions, sharedptrs, arrays)
        sharedptrs[-1] = 0
        positions.append(None)
        sharedptrs.append(None)
        positions[pos + cls.CONTENT] = \
          awkward1._connect._numba.arrayview.tolookup(form.content,
                                                      positions,
                                                      sharedptrs,
                                                      arrays)
        return pos

    @classmethod
    def from_form(cls, form):
        return RegularArrayType(
            awkward1._connect._numba.arrayview.tonumbatype(form.content),
            form.size,
            cls.from_form_identities(form),
            form.parameters)

    def __init__(self, contenttype, size, identitiestype, parameters):
        super(RegularArrayType, self).__init__(
            name="awkward1.RegularArrayType({0}, {1}, {2}, {3})".format(
            contenttype.name,
            size,
            identitiestype.name,
            json.dumps(parameters)))
        self.contenttype = contenttype
        self.size = size
        self.identitiestype = identitiestype
        self.parameters = parameters

    def form_fill(self, pos, layout, lookup):
        lookup.sharedptrs_hold[pos] = layout._persistent_shared_ptr
        lookup.sharedptrs[pos] = lookup.sharedptrs_hold[pos].ptr()
        self.form_fill_identities(pos, layout, lookup)

        self.contenttype.form_fill(lookup.arrayptrs[pos + self.CONTENT],
                                   layout.content,
                                   lookup)

    def tolayout(self, lookup, pos, fields):
        content = self.contenttype.tolayout(
                    lookup, lookup.positions[pos + self.CONTENT], fields)
        return awkward1.layout.RegularArray(content,
                                            self.size,
                                            parameters=self.parameters)

    def hasfield(self, key):
        return self.contenttype.hasfield(key)

    def getitem_at(self, viewtype):
        return awkward1._connect._numba.arrayview.wrap(self.contenttype,
                                                       viewtype,
                                                       None)

    def lower_getitem_at(self,
                         context,
                         builder,
                         rettype,
                         viewtype,
                         viewval,
                         viewproxy,
                         attype,
                         atval,
                         wrapneg,
                         checkbounds):
        whichpos = posat(context, builder, viewproxy.pos, self.CONTENT)
        nextpos = getat(context, builder, viewproxy.arrayptrs, whichpos)

        atval = regularize_atval(context,
                                 builder,
                                 viewproxy,
                                 attype,
                                 atval,
                                 wrapneg,
                                 checkbounds)

        size = context.get_constant(numba.intp, self.size)
        start = builder.mul(builder.add(viewproxy.start, atval), size)
        stop  = builder.add(start, size)

        proxyout = context.make_helper(builder, rettype)
        proxyout.pos        = nextpos
        proxyout.start      = start
        proxyout.stop       = stop
        proxyout.arrayptrs  = viewproxy.arrayptrs
        proxyout.sharedptrs = viewproxy.sharedptrs
        proxyout.pylookup   = viewproxy.pylookup
        return proxyout._getvalue()

class ListArrayType(ContentType):
    IDENTITIES = 0
    STARTS = 1
    STOPS = 2
    CONTENT = 3

    @classmethod
    def tolookup(cls, layout, positions, sharedptrs, arrays):
        if isinstance(layout, (awkward1.layout.ListArray32,
                               awkward1.layout.ListArrayU32,
                               awkward1.layout.ListArray64)):
            starts = numpy.asarray(layout.starts)
            stops = numpy.asarray(layout.stops)
        elif isinstance(layout, (awkward1.layout.ListOffsetArray32,
                                 awkward1.layout.ListOffsetArrayU32,
                                 awkward1.layout.ListOffsetArray64)):
            offsets = numpy.asarray(layout.offsets)
            starts = offsets[:-1]
            stops = offsets[1:]

        pos = len(positions)
        cls.tolookup_identities(layout, positions, sharedptrs, arrays)
        sharedptrs[-1] = layout._persistent_shared_ptr
        positions.append(starts)
        sharedptrs.append(None)
        arrays.append(starts)
        positions.append(stops)
        sharedptrs.append(None)
        arrays.append(stops)
        positions.append(None)
        sharedptrs.append(None)
        positions[pos + cls.CONTENT] = \
          awkward1._connect._numba.arrayview.tolookup(layout.content,
                                                      positions,
                                                      sharedptrs,
                                                      arrays)
        return pos

    @classmethod
    def form_tolookup(cls, form, positions, sharedptrs, arrays):
        pos = len(positions)
        cls.form_tolookup_identities(form, positions, sharedptrs, arrays)
        sharedptrs[-1] = 0
        positions.append(0)
        sharedptrs.append(None)
        arrays.append(0)
        positions.append(0)
        sharedptrs.append(None)
        arrays.append(0)
        positions.append(None)
        sharedptrs.append(None)
        positions[pos + cls.CONTENT] = \
          awkward1._connect._numba.arrayview.tolookup(form.content,
                                                      positions,
                                                      sharedptrs,
                                                      arrays)
        return pos

    @classmethod
    def from_form(cls, form):
        return ListArrayType(
            cls.from_form_index(form.starts
                                  if isinstance(form, awkward1.forms.ListForm)
                                  else form.offsets),
            awkward1._connect._numba.arrayview.tonumbatype(form.content),
            cls.from_form_identities(form),
            form.parameters)

    def __init__(self, indextype, contenttype, identitiestype, parameters):
        super(ListArrayType, self).__init__(
            name="awkward1.ListArrayType({0}, {1}, {2}, {3})".format(
              indextype.name,
              contenttype.name,
              identitiestype.name,
              json.dumps(parameters)))
        self.indextype = indextype
        self.contenttype = contenttype
        self.identitiestype = identitiestype
        self.parameters = parameters

    def form_fill(self, pos, layout, lookup):
        lookup.sharedptrs_hold[pos] = layout._persistent_shared_ptr
        lookup.sharedptrs[pos] = lookup.sharedptrs_hold[pos].ptr()
        self.form_fill_identities(pos, layout, lookup)

        if isinstance(layout, (awkward1.layout.ListArray32,
                               awkward1.layout.ListArrayU32,
                               awkward1.layout.ListArray64)):
            starts = numpy.asarray(layout.starts)
            stops = numpy.asarray(layout.stops)
        elif isinstance(layout, (awkward1.layout.ListOffsetArray32,
                                 awkward1.layout.ListOffsetArrayU32,
                                 awkward1.layout.ListOffsetArray64)):
            offsets = numpy.asarray(layout.offsets)
            starts = offsets[:-1]
            stops = offsets[1:]

        lookup.original_positions[pos + self.STARTS] = starts
        lookup.original_positions[pos + self.STOPS] = stops
        lookup.arrayptrs[pos + self.STARTS] = starts.ctypes.data
        lookup.arrayptrs[pos + self.STOPS] = stops.ctypes.data

        self.contenttype.form_fill(lookup.arrayptrs[pos + self.CONTENT],
                                   layout.content,
                                   lookup)

    def ListArrayOf(self):
        if self.indextype.dtype.bitwidth == 32 and self.indextype.dtype.signed:
            return awkward1.layout.ListArray32
        elif self.indextype.dtype.bitwidth == 32:
            return awkward1.layout.ListArrayU32
        elif (self.indextype.dtype.bitwidth == 64 and
              self.indextype.dtype.signed):
            return awkward1.layout.ListArray64
        else:
            raise AssertionError(
                    "no ListArray* type for array: {0}".format(indextype))

    def tolayout(self, lookup, pos, fields):
        starts = self.IndexOf(self.indextype)(
                   lookup.original_positions[pos + self.STARTS])
        stops = self.IndexOf(self.indextype)(
                   lookup.original_positions[pos + self.STOPS])
        content = self.contenttype.tolayout(
                   lookup,
                   lookup.positions[pos + self.CONTENT],
                   fields)
        return self.ListArrayOf()(starts,
                                  stops,
                                  content,
                                  parameters=self.parameters)

    def hasfield(self, key):
        return self.contenttype.hasfield(key)

    def getitem_at(self, viewtype):
        return awkward1._connect._numba.arrayview.wrap(self.contenttype,
                                                       viewtype,
                                                       None)

    def lower_getitem_at(self,
                         context,
                         builder,
                         rettype,
                         viewtype,
                         viewval,
                         viewproxy,
                         attype,
                         atval,
                         wrapneg,
                         checkbounds):
        whichpos = posat(context, builder, viewproxy.pos, self.CONTENT)
        nextpos = getat(context, builder, viewproxy.arrayptrs, whichpos)

        atval = regularize_atval(context,
                                 builder,
                                 viewproxy,
                                 attype,
                                 atval,
                                 wrapneg,
                                 checkbounds)

        startspos = posat(context, builder, viewproxy.pos, self.STARTS)
        startsptr = getat(context, builder, viewproxy.arrayptrs, startspos)
        startsarraypos = builder.add(viewproxy.start, atval)
        start = getat(context,
                      builder,
                      startsptr,
                      startsarraypos,
                      self.indextype.dtype)

        stopspos = posat(context, builder, viewproxy.pos, self.STOPS)
        stopsptr = getat(context, builder, viewproxy.arrayptrs, stopspos)
        stopsarraypos = builder.add(viewproxy.start, atval)
        stop = getat(context,
                     builder,
                     stopsptr,
                     stopsarraypos,
                     self.indextype.dtype)

        proxyout = context.make_helper(builder, rettype)
        proxyout.pos        = nextpos
        proxyout.start      = awkward1._connect._numba.castint(
                                context,
                                builder,
                                self.indextype.dtype,
                                numba.intp,
                                start)
        proxyout.stop       = awkward1._connect._numba.castint(
                                context,
                                builder,
                                self.indextype.dtype,
                                numba.intp,
                                stop)
        proxyout.arrayptrs  = viewproxy.arrayptrs
        proxyout.sharedptrs = viewproxy.sharedptrs
        proxyout.pylookup   = viewproxy.pylookup
        return proxyout._getvalue()

class IndexedArrayType(ContentType):
    IDENTITIES = 0
    INDEX = 1
    CONTENT = 2

    @classmethod
    def tolookup(cls, layout, positions, sharedptrs, arrays):
        pos = len(positions)
        cls.tolookup_identities(layout, positions, sharedptrs, arrays)
        sharedptrs[-1] = layout._persistent_shared_ptr
        arrays.append(numpy.asarray(layout.index))
        positions.append(arrays[-1])
        sharedptrs.append(None)
        positions.append(None)
        sharedptrs.append(None)
        positions[pos + cls.CONTENT] = \
          awkward1._connect._numba.arrayview.tolookup(layout.content,
                                                      positions,
                                                      sharedptrs,
                                                      arrays)
        return pos

    @classmethod
    def form_tolookup(cls, form, positions, sharedptrs, arrays):
        pos = len(positions)
        cls.form_tolookup_identities(form, positions, sharedptrs, arrays)
        sharedptrs[-1] = 0
        arrays.append(0)
        positions.append(0)
        sharedptrs.append(None)
        positions.append(None)
        sharedptrs.append(None)
        positions[pos + cls.CONTENT] = \
          awkward1._connect._numba.arrayview.tolookup(form.content,
                                                      positions,
                                                      sharedptrs,
                                                      arrays)
        return pos

    @classmethod
    def from_form(cls, form):
        return IndexedArrayType(
            cls.from_form_index(form.index),
            awkward1._connect._numba.arrayview.tonumbatype(form.content),
            cls.from_form_identities(form),
            form.parameters)

    def __init__(self, indextype, contenttype, identitiestype, parameters):
        super(IndexedArrayType, self).__init__(
            name="awkward1.IndexedArrayType({0}, {1}, {2}, {3})".format(
              indextype.name,
              contenttype.name,
              identitiestype.name,
              json.dumps(parameters)))
        self.indextype = indextype
        self.contenttype = contenttype
        self.identitiestype = identitiestype
        self.parameters = parameters

    def form_fill(self, pos, layout, lookup):
        lookup.sharedptrs_hold[pos] = layout._persistent_shared_ptr
        lookup.sharedptrs[pos] = lookup.sharedptrs_hold[pos].ptr()
        self.form_fill_identities(pos, layout, lookup)

        index = numpy.asarray(layout.index)
        lookup.original_positions[pos + self.INDEX] = index
        lookup.arrayptrs[pos + self.INDEX] = index.ctypes.data

        self.contenttype.form_fill(lookup.arrayptrs[pos + self.CONTENT],
                                   layout.content,
                                   lookup)

    def IndexedArrayOf(self):
        if self.indextype.dtype.bitwidth == 32 and self.indextype.dtype.signed:
            return awkward1.layout.IndexedArray32
        elif self.indextype.dtype.bitwidth == 32:
            return awkward1.layout.IndexedArrayU32
        elif (self.indextype.dtype.bitwidth == 64 and
              self.indextype.dtype.signed):
            return awkward1.layout.IndexedArray64
        else:
            raise AssertionError(
                    "no IndexedArray* type for array: {0}".format(
                      self.indextype))

    def tolayout(self, lookup, pos, fields):
        index = self.IndexOf(self.indextype)(
                  lookup.original_positions[pos + self.INDEX])
        content = self.contenttype.tolayout(
                    lookup, lookup.positions[pos + self.CONTENT], fields)
        return self.IndexedArrayOf()(index,
                                     content,
                                     parameters=self.parameters)

    def hasfield(self, key):
        return self.contenttype.hasfield(key)

    def getitem_at(self, viewtype):
        return self.contenttype.getitem_at_check(viewtype)

    def lower_getitem_at(self,
                         context,
                         builder,
                         rettype,
                         viewtype,
                         viewval,
                         viewproxy,
                         attype,
                         atval,
                         wrapneg,
                         checkbounds):
        whichpos = posat(context, builder, viewproxy.pos, self.CONTENT)
        nextpos = getat(context, builder, viewproxy.arrayptrs, whichpos)

        atval = regularize_atval(context,
                                 builder,
                                 viewproxy,
                                 attype,
                                 atval,
                                 wrapneg,
                                 checkbounds)

        indexpos = posat(context, builder, viewproxy.pos, self.INDEX)
        indexptr = getat(context, builder, viewproxy.arrayptrs, indexpos)
        indexarraypos = builder.add(viewproxy.start, atval)
        nextat = getat(context,
                       builder,
                       indexptr,
                       indexarraypos,
                       self.indextype.dtype)

        nextviewtype = awkward1._connect._numba.arrayview.wrap(
                         self.contenttype, viewtype, None)
        proxynext = context.make_helper(builder, nextviewtype)
        proxynext.pos        = nextpos
        proxynext.start      = viewproxy.start
        proxynext.stop       = builder.add(
                                 awkward1._connect._numba.castint(
                                   context,
                                   builder,
                                   self.indextype.dtype,
                                   numba.intp,
                                   nextat),
                                 builder.add(viewproxy.start,
                                             context.get_constant(
                                               numba.intp, 1)))
        proxynext.arrayptrs  = viewproxy.arrayptrs
        proxynext.sharedptrs = viewproxy.sharedptrs
        proxynext.pylookup   = viewproxy.pylookup

        return self.contenttype.lower_getitem_at_check(context,
                                                       builder,
                                                       rettype,
                                                       nextviewtype,
                                                       proxynext._getvalue(),
                                                       proxynext,
                                                       numba.intp,
                                                       nextat,
                                                       False,
                                                       False)

class IndexedOptionArrayType(ContentType):
    IDENTITIES = 0
    INDEX = 1
    CONTENT = 2

    @classmethod
    def tolookup(cls, layout, positions, sharedptrs, arrays):
        pos = len(positions)
        cls.tolookup_identities(layout, positions, sharedptrs, arrays)
        sharedptrs[-1] = layout._persistent_shared_ptr
        arrays.append(numpy.asarray(layout.index))
        positions.append(arrays[-1])
        sharedptrs.append(None)
        positions.append(None)
        sharedptrs.append(None)
        positions[pos + cls.CONTENT] = \
          awkward1._connect._numba.arrayview.tolookup(layout.content,
                                                      positions,
                                                      sharedptrs,
                                                      arrays)
        return pos

    @classmethod
    def form_tolookup(cls, form, positions, sharedptrs, arrays):
        pos = len(positions)
        cls.form_tolookup_identities(form, positions, sharedptrs, arrays)
        sharedptrs[-1] = 0
        arrays.append(0)
        positions.append(0)
        sharedptrs.append(None)
        positions.append(None)
        sharedptrs.append(None)
        positions[pos + cls.CONTENT] = \
          awkward1._connect._numba.arrayview.tolookup(form.content,
                                                      positions,
                                                      sharedptrs,
                                                      arrays)
        return pos

    @classmethod
    def from_form(cls, form):
        return IndexedOptionArrayType(
            cls.from_form_index(form.index),
            awkward1._connect._numba.arrayview.tonumbatype(form.content),
            cls.from_form_identities(form),
            form.parameters)

    def __init__(self, indextype, contenttype, identitiestype, parameters):
        super(IndexedOptionArrayType, self).__init__(
            name="awkward1.IndexedOptionArrayType({0}, {1}, {2}, {3})".format(
              indextype.name,
              contenttype.name,
              identitiestype.name,
              json.dumps(parameters)))
        self.indextype = indextype
        self.contenttype = contenttype
        self.identitiestype = identitiestype
        self.parameters = parameters

    def form_fill(self, pos, layout, lookup):
        lookup.sharedptrs_hold[pos] = layout._persistent_shared_ptr
        lookup.sharedptrs[pos] = lookup.sharedptrs_hold[pos].ptr()
        self.form_fill_identities(pos, layout, lookup)

        index = numpy.asarray(layout.index)
        lookup.original_positions[pos + self.INDEX] = index
        lookup.arrayptrs[pos + self.INDEX] = index.ctypes.data

        self.contenttype.form_fill(lookup.arrayptrs[pos + self.CONTENT],
                                   layout.content,
                                   lookup)

    def IndexedOptionArrayOf(self):
        if self.indextype.dtype.bitwidth == 32 and self.indextype.dtype.signed:
            return awkward1.layout.IndexedOptionArray32
        elif (self.indextype.dtype.bitwidth == 64 and
              self.indextype.dtype.signed):
            return awkward1.layout.IndexedOptionArray64
        else:
            raise AssertionError(
                    "no IndexedOptionArray* type for array: {0}".format(
                      self.indextype))

    def tolayout(self, lookup, pos, fields):
        index = self.IndexOf(self.indextype)(
            lookup.original_positions[pos + self.INDEX])
        content = self.contenttype.tolayout(
            lookup, lookup.positions[pos + self.CONTENT], fields)
        return self.IndexedOptionArrayOf()(index,
                                           content,
                                           parameters=self.parameters)

    def hasfield(self, key):
        return self.contenttype.hasfield(key)

    def getitem_at(self, viewtype):
        return numba.types.optional(
                 self.contenttype.getitem_at_check(viewtype))

    def lower_getitem_at(self,
                         context,
                         builder,
                         rettype,
                         viewtype,
                         viewval,
                         viewproxy,
                         attype,
                         atval,
                         wrapneg,
                         checkbounds):
        whichpos = posat(context, builder, viewproxy.pos, self.CONTENT)
        nextpos = getat(context, builder, viewproxy.arrayptrs, whichpos)

        atval = regularize_atval(context,
                                 builder,
                                 viewproxy,
                                 attype,
                                 atval,
                                 wrapneg,
                                 checkbounds)

        indexpos = posat(context, builder, viewproxy.pos, self.INDEX)
        indexptr = getat(context, builder, viewproxy.arrayptrs, indexpos)
        indexarraypos = builder.add(viewproxy.start, atval)
        nextat = getat(context,
                       builder,
                       indexptr,
                       indexarraypos,
                       self.indextype.dtype)

        output = context.make_helper(builder, rettype)

        with builder.if_else(builder.icmp_signed(
               "<",
               nextat,
               context.get_constant(self.indextype.dtype, 0))) as (isnone,
                                                                   isvalid):
            with isnone:
                output.valid = numba.core.cgutils.false_bit
                output.data = numba.core.cgutils.get_null_value(
                                output.data.type)

            with isvalid:
                nextviewtype = awkward1._connect._numba.arrayview.wrap(
                                 self.contenttype, viewtype, None)
                proxynext = context.make_helper(builder, nextviewtype)
                proxynext.pos        = nextpos
                proxynext.start      = viewproxy.start
                proxynext.stop       = builder.add(
                                         awkward1._connect._numba.castint(
                                           context,
                                           builder,
                                           self.indextype.dtype,
                                           numba.intp,
                                           nextat),
                                         builder.add(viewproxy.start,
                                                     context.get_constant(
                                                       numba.intp, 1)))
                proxynext.arrayptrs  = viewproxy.arrayptrs
                proxynext.sharedptrs = viewproxy.sharedptrs
                proxynext.pylookup   = viewproxy.pylookup

                outdata = self.contenttype.lower_getitem_at_check(
                            context,
                            builder,
                            rettype.type,
                            nextviewtype,
                            proxynext._getvalue(),
                            proxynext,
                            numba.intp,
                            nextat,
                            False,
                            False)

                output.valid = numba.core.cgutils.true_bit
                output.data = outdata

        return output._getvalue()

class ByteMaskedArrayType(ContentType):
    IDENTITIES = 0
    MASK = 1
    CONTENT = 2

    @classmethod
    def tolookup(cls, layout, positions, sharedptrs, arrays):
        pos = len(positions)
        cls.tolookup_identities(layout, positions, sharedptrs, arrays)
        sharedptrs[-1] = layout._persistent_shared_ptr
        arrays.append(numpy.asarray(layout.mask))
        positions.append(arrays[-1])
        sharedptrs.append(None)
        positions.append(None)
        sharedptrs.append(None)
        positions[pos + cls.CONTENT] = \
          awkward1._connect._numba.arrayview.tolookup(layout.content,
                                                      positions,
                                                      sharedptrs,
                                                      arrays)
        return pos

    @classmethod
    def form_tolookup(cls, form, positions, sharedptrs, arrays):
        pos = len(positions)
        cls.form_tolookup_identities(form, positions, sharedptrs, arrays)
        sharedptrs[-1] = 0
        arrays.append(0)
        positions.append(0)
        sharedptrs.append(None)
        positions.append(None)
        sharedptrs.append(None)
        positions[pos + cls.CONTENT] = \
          awkward1._connect._numba.arrayview.tolookup(form.content,
                                                      positions,
                                                      sharedptrs,
                                                      arrays)
        return pos

    @classmethod
    def from_form(cls, form):
        return ByteMaskedArrayType(
            cls.from_form_index(form.mask),
            awkward1._connect._numba.arrayview.tonumbatype(form.content),
            form.valid_when,
            cls.from_form_identities(form),
            form.parameters)

    def __init__(self,
                 masktype,
                 contenttype,
                 valid_when,
                 identitiestype,
                 parameters):
        super(ByteMaskedArrayType, self).__init__(
            name="awkward1.ByteMaskedArrayType({0}, {1}, {2}, {3}, "
            "{4})".format(masktype.name,
                          contenttype.name,
                          valid_when,
                          identitiestype.name,
                          json.dumps(parameters)))
        self.masktype = masktype
        self.contenttype = contenttype
        self.valid_when = valid_when
        self.identitiestype = identitiestype
        self.parameters = parameters

    def form_fill(self, pos, layout, lookup):
        lookup.sharedptrs_hold[pos] = layout._persistent_shared_ptr
        lookup.sharedptrs[pos] = lookup.sharedptrs_hold[pos].ptr()
        self.form_fill_identities(pos, layout, lookup)

        mask = numpy.asarray(layout.mask)
        lookup.original_positions[pos + self.MASK] = mask
        lookup.arrayptrs[pos + self.MASK] = mask.ctypes.data

        self.contenttype.form_fill(lookup.arrayptrs[pos + self.CONTENT],
                                   layout.content,
                                   lookup)

    def tolayout(self, lookup, pos, fields):
        mask = self.IndexOf(self.masktype)(
            lookup.original_positions[pos + self.MASK])
        content = self.contenttype.tolayout(
            lookup, lookup.positions[pos + self.CONTENT], fields)
        return awkward1.layout.ByteMaskedArray(mask,
                                               content,
                                               self.valid_when,
                                               parameters=self.parameters)

    def hasfield(self, key):
        return self.contenttype.hasfield(key)

    def getitem_at(self, viewtype):
        return numba.types.optional(
                 self.contenttype.getitem_at_check(viewtype))

    def lower_getitem_at(self,
                         context,
                         builder,
                         rettype,
                         viewtype,
                         viewval,
                         viewproxy,
                         attype,
                         atval,
                         wrapneg,
                         checkbounds):
        whichpos = posat(context, builder, viewproxy.pos, self.CONTENT)
        nextpos = getat(context, builder, viewproxy.arrayptrs, whichpos)

        atval = regularize_atval(context,
                                 builder,
                                 viewproxy,
                                 attype,
                                 atval,
                                 wrapneg,
                                 checkbounds)

        maskpos = posat(context, builder, viewproxy.pos, self.MASK)
        maskptr = getat(context, builder, viewproxy.arrayptrs, maskpos)
        maskarraypos = builder.add(viewproxy.start, atval)
        byte = getat(context,
                     builder,
                     maskptr,
                     maskarraypos,
                     self.masktype.dtype)

        output = context.make_helper(builder, rettype)

        with builder.if_else(
               builder.icmp_signed("==",
                 builder.icmp_signed("!=",
                                     byte,
                                     context.get_constant(numba.int8, 0)),
                                   context.get_constant(numba.int8,
                                                        int(self.valid_when)
                          ))) as (isvalid, isnone):
            with isvalid:
                nextviewtype = awkward1._connect._numba.arrayview.wrap(
                                 self.contenttype, viewtype, None)
                proxynext = context.make_helper(builder, nextviewtype)
                proxynext.pos        = nextpos
                proxynext.start      = viewproxy.start
                proxynext.stop       = viewproxy.stop
                proxynext.arrayptrs  = viewproxy.arrayptrs
                proxynext.sharedptrs = viewproxy.sharedptrs
                proxynext.pylookup   = viewproxy.pylookup

                outdata = self.contenttype.lower_getitem_at_check(
                            context,
                            builder,
                            rettype.type,
                            nextviewtype,
                            proxynext._getvalue(),
                            proxynext,
                            numba.intp,
                            atval,
                            False,
                            False)

                output.valid = numba.core.cgutils.true_bit
                output.data = outdata

            with isnone:
                output.valid = numba.core.cgutils.false_bit
                output.data = numba.core.cgutils.get_null_value(
                                output.data.type)

        return output._getvalue()

class BitMaskedArrayType(ContentType):
    IDENTITIES = 0
    MASK = 1
    CONTENT = 2

    @classmethod
    def tolookup(cls, layout, positions, sharedptrs, arrays):
        pos = len(positions)
        cls.tolookup_identities(layout, positions, sharedptrs, arrays)
        sharedptrs[-1] = layout._persistent_shared_ptr
        arrays.append(numpy.asarray(layout.mask))
        positions.append(arrays[-1])
        sharedptrs.append(None)
        positions.append(None)
        sharedptrs.append(None)
        positions[pos + cls.CONTENT] = \
          awkward1._connect._numba.arrayview.tolookup(layout.content,
                                                      positions,
                                                      sharedptrs,
                                                      arrays)
        return pos

    @classmethod
    def form_tolookup(cls, form, positions, sharedptrs, arrays):
        pos = len(positions)
        cls.form_tolookup_identities(form, positions, sharedptrs, arrays)
        sharedptrs[-1] = 0
        arrays.append(0)
        positions.append(0)
        sharedptrs.append(None)
        positions.append(None)
        sharedptrs.append(None)
        positions[pos + cls.CONTENT] = \
          awkward1._connect._numba.arrayview.tolookup(form.content,
                                                      positions,
                                                      sharedptrs,
                                                      arrays)
        return pos

    @classmethod
    def from_form(cls, form):
        return BitMaskedArrayType(
            cls.from_form_index(form.mask),
            awkward1._connect._numba.arrayview.tonumbatype(form.content),
            form.valid_when,
            form.lsb_order,
            cls.from_form_identities(form),
            form.parameters)

    def __init__(self,
                 masktype,
                 contenttype,
                 valid_when,
                 lsb_order,
                 identitiestype,
                 parameters):
        super(BitMaskedArrayType, self).__init__(
            name="awkward1.BitMaskedArrayType({0}, {1}, {2}, {3}, {4}, "
            "{5})".format(masktype.name,
                          contenttype.name,
                          valid_when,
                          lsb_order,
                          identitiestype.name,
                          json.dumps(parameters)))
        self.masktype = masktype
        self.contenttype = contenttype
        self.valid_when = valid_when
        self.lsb_order = lsb_order
        self.identitiestype = identitiestype
        self.parameters = parameters

    def form_fill(self, pos, layout, lookup):
        lookup.sharedptrs_hold[pos] = layout._persistent_shared_ptr
        lookup.sharedptrs[pos] = lookup.sharedptrs_hold[pos].ptr()
        self.form_fill_identities(pos, layout, lookup)

        mask = numpy.asarray(layout.mask)
        lookup.original_positions[pos + self.MASK] = mask
        lookup.arrayptrs[pos + self.MASK] = mask.ctypes.data

        self.contenttype.form_fill(lookup.arrayptrs[pos + self.CONTENT],
                                   layout.content,
                                   lookup)

    def tolayout(self, lookup, pos, fields):
        mask = self.IndexOf(self.masktype)(
            lookup.original_positions[pos + self.MASK])
        content = self.contenttype.tolayout(
            lookup, lookup.positions[pos + self.CONTENT], fields)
        return awkward1.layout.BitMaskedArray(mask,
                                              content,
                                              self.valid_when,
                                              len(content),
                                              self.lsb_order,
                                              parameters=self.parameters)

    def hasfield(self, key):
        return self.contenttype.hasfield(key)

    def getitem_at(self, viewtype):
        return numba.types.optional(
                 self.contenttype.getitem_at_check(viewtype))

    def lower_getitem_at(self,
                         context,
                         builder,
                         rettype,
                         viewtype,
                         viewval,
                         viewproxy,
                         attype,
                         atval,
                         wrapneg,
                         checkbounds):
        whichpos = posat(context, builder, viewproxy.pos, self.CONTENT)
        nextpos = getat(context, builder, viewproxy.arrayptrs, whichpos)

        atval = regularize_atval(context,
                                 builder,
                                 viewproxy,
                                 attype,
                                 atval,
                                 wrapneg,
                                 checkbounds)
        bitatval = builder.sdiv(atval, context.get_constant(numba.intp, 8))
        shiftval = awkward1._connect._numba.castint(
                     context,
                     builder,
                     numba.intp,
                     numba.uint8,
                     builder.srem(atval, context.get_constant(numba.intp, 8)))

        maskpos = posat(context, builder, viewproxy.pos, self.MASK)
        maskptr = getat(context, builder, viewproxy.arrayptrs, maskpos)
        maskarraypos = builder.add(viewproxy.start, bitatval)
        byte = getat(context,
                     builder,
                     maskptr,
                     maskarraypos,
                     self.masktype.dtype)
        if self.lsb_order:
            # ((byte >> ((uint8_t)shift)) & ((uint8_t)1))
            asbool = builder.and_(
                       builder.lshr(byte, shiftval),
                       context.get_constant(numba.uint8, 1))
        else:
            # ((byte << ((uint8_t)shift)) & ((uint8_t)128))
            asbool = builder.and_(
                       builder.shl(byte, shiftval),
                       context.get_constant(numba.uint8, 128))

        output = context.make_helper(builder, rettype)

        with builder.if_else(
               builder.icmp_signed("==",
                 builder.icmp_signed("!=",
                                     asbool,
                                     context.get_constant(numba.uint8, 0)),
                                   context.get_constant(numba.uint8,
                                                        int(self.valid_when)
                          ))) as (isvalid, isnone):
            with isvalid:
                nextviewtype = awkward1._connect._numba.arrayview.wrap(
                                 self.contenttype, viewtype, None)
                proxynext = context.make_helper(builder, nextviewtype)
                proxynext.pos        = nextpos
                proxynext.start      = viewproxy.start
                proxynext.stop       = viewproxy.stop
                proxynext.arrayptrs  = viewproxy.arrayptrs
                proxynext.sharedptrs = viewproxy.sharedptrs
                proxynext.pylookup   = viewproxy.pylookup

                outdata = self.contenttype.lower_getitem_at_check(
                            context,
                            builder,
                            rettype.type,
                            nextviewtype,
                            proxynext._getvalue(),
                            proxynext,
                            numba.intp,
                            atval,
                            False,
                            False)

                output.valid = numba.core.cgutils.true_bit
                output.data = outdata

            with isnone:
                output.valid = numba.core.cgutils.false_bit
                output.data = numba.core.cgutils.get_null_value(
                                output.data.type)

        return output._getvalue()

class UnmaskedArrayType(ContentType):
    IDENTITIES = 0
    CONTENT = 1

    @classmethod
    def tolookup(cls, layout, positions, sharedptrs, arrays):
        pos = len(positions)
        cls.tolookup_identities(layout, positions, sharedptrs, arrays)
        sharedptrs[-1] = layout._persistent_shared_ptr
        positions.append(None)
        sharedptrs.append(None)
        positions[pos + cls.CONTENT] = \
          awkward1._connect._numba.arrayview.tolookup(layout.content,
                                                      positions,
                                                      sharedptrs,
                                                      arrays)
        return pos

    @classmethod
    def form_tolookup(cls, form, positions, sharedptrs, arrays):
        pos = len(positions)
        cls.form_tolookup_identities(form, positions, sharedptrs, arrays)
        sharedptrs[-1] = 0
        positions.append(None)
        sharedptrs.append(None)
        positions[pos + cls.CONTENT] = \
          awkward1._connect._numba.arrayview.tolookup(form.content,
                                                      positions,
                                                      sharedptrs,
                                                      arrays)
        return pos

    @classmethod
    def from_form(cls, form):
        return UnmaskedArrayType(
            awkward1._connect._numba.arrayview.tonumbatype(form.content),
            cls.from_form_identities(form),
            form.parameters)

    def __init__(self, contenttype, identitiestype, parameters):
        super(UnmaskedArrayType, self).__init__(
            name="awkward1.UnmaskedArrayType({0}, {1}, {2})".format(
              contenttype.name,
              identitiestype.name,
              json.dumps(parameters)))
        self.contenttype = contenttype
        self.identitiestype = identitiestype
        self.parameters = parameters

    def form_fill(self, pos, layout, lookup):
        lookup.sharedptrs_hold[pos] = layout._persistent_shared_ptr
        lookup.sharedptrs[pos] = lookup.sharedptrs_hold[pos].ptr()
        self.form_fill_identities(pos, layout, lookup)

        self.contenttype.form_fill(lookup.arrayptrs[pos + self.CONTENT],
                                   layout.content,
                                   lookup)

    def tolayout(self, lookup, pos, fields):
        content = self.contenttype.tolayout(
            lookup, lookup.positions[pos + self.CONTENT], fields)
        return awkward1.layout.UnmaskedArray(content,
                                             parameters=self.parameters)

    def hasfield(self, key):
        return self.contenttype.hasfield(key)

    def getitem_at(self, viewtype):
        return numba.types.optional(
                 self.contenttype.getitem_at_check(viewtype))

    def lower_getitem_at(self,
                         context,
                         builder,
                         rettype,
                         viewtype,
                         viewval,
                         viewproxy,
                         attype,
                         atval,
                         wrapneg,
                         checkbounds):
        whichpos = posat(context, builder, viewproxy.pos, self.CONTENT)
        nextpos = getat(context, builder, viewproxy.arrayptrs, whichpos)

        atval = regularize_atval(context,
                                 builder,
                                 viewproxy,
                                 attype,
                                 atval,
                                 wrapneg,
                                 checkbounds)

        output = context.make_helper(builder, rettype)

        nextviewtype = awkward1._connect._numba.arrayview.wrap(
                         self.contenttype, viewtype, None)
        proxynext = context.make_helper(builder, nextviewtype)
        proxynext.pos        = nextpos
        proxynext.start      = viewproxy.start
        proxynext.stop       = viewproxy.stop
        proxynext.arrayptrs  = viewproxy.arrayptrs
        proxynext.sharedptrs = viewproxy.sharedptrs
        proxynext.pylookup   = viewproxy.pylookup

        outdata = self.contenttype.lower_getitem_at_check(
                    context,
                    builder,
                    rettype.type,
                    nextviewtype,
                    proxynext._getvalue(),
                    proxynext,
                    numba.intp,
                    atval,
                    False,
                    False)

        output.valid = numba.core.cgutils.true_bit
        output.data = outdata

        return output._getvalue()

class RecordArrayType(ContentType):
    IDENTITIES = 0
    CONTENTS = 1

    @classmethod
    def tolookup(cls, layout, positions, sharedptrs, arrays):
        pos = len(positions)
        cls.tolookup_identities(layout, positions, sharedptrs, arrays)
        sharedptrs[-1] = layout._persistent_shared_ptr
        positions.extend([None] * layout.numfields)
        sharedptrs.extend([None] * layout.numfields)
        for i, content in enumerate(layout.contents):
            positions[pos + cls.CONTENTS + i] = \
              awkward1._connect._numba.arrayview.tolookup(content,
                                                          positions,
                                                          sharedptrs,
                                                          arrays)
        return pos

    @classmethod
    def form_tolookup(cls, form, positions, sharedptrs, arrays):
        pos = len(positions)
        cls.form_tolookup_identities(form, positions, sharedptrs, arrays)
        sharedptrs[-1] = 0
        positions.extend([None] * form.numfields)
        sharedptrs.extend([None] * form.numfields)
        if form.istuple:
            for i, (n, content) in enumerate(form.contents.items()):
                positions[pos + cls.CONTENTS + i] = \
                  awkward1._connect._numba.arrayview.tolookup(content,
                                                              positions,
                                                              sharedptrs,
                                                              arrays)
        else:
            for i, (n, content) in enumerate(form.contents.items()):
                positions[pos + cls.CONTENTS + i] = \
                  awkward1._connect._numba.arrayview.tolookup(content,
                                                              positions,
                                                              sharedptrs,
                                                              arrays)
        return pos

    @classmethod
    def from_form(cls, form):
        contents = []
        if form.istuple:
            recordlookup = None
            for n, x in form.contents.items():
                contents.append(
                    awkward1._connect._numba.arrayview.tonumbatype(x))
        else:
            recordlookup = []
            for n, x in form.contents.items():
                contents.append(
                    awkward1._connect._numba.arrayview.tonumbatype(x))
                recordlookup.append(n)

        return RecordArrayType(
            contents,
            recordlookup,
            cls.from_form_identities(form),
            form.parameters)

    def __init__(self, contenttypes, recordlookup, identitiestype, parameters):
        super(RecordArrayType, self).__init__(
            name="awkward1.RecordArrayType(({0}{1}), ({2}), {3}, {4})".format(
              ", ".join(x.name for x in contenttypes),
              "," if len(contenttypes) == 1 else "",
              "None" if recordlookup is None else repr(tuple(recordlookup)),
              identitiestype.name,
              json.dumps(parameters)))
        self.contenttypes = contenttypes
        self.recordlookup = recordlookup
        self.identitiestype = identitiestype
        self.parameters = parameters

    def form_fill(self, pos, layout, lookup):
        lookup.sharedptrs_hold[pos] = layout._persistent_shared_ptr
        lookup.sharedptrs[pos] = lookup.sharedptrs_hold[pos].ptr()
        self.form_fill_identities(pos, layout, lookup)

        for i, contenttype in enumerate(self.contenttypes):
            contenttype.form_fill(lookup.arrayptrs[pos + self.CONTENTS + i],
                                  layout.field(i),
                                  lookup)

    def fieldindex(self, key):
        out = -1
        if self.recordlookup is not None:
            for i, x in enumerate(self.recordlookup):
                if x == key:
                    out = i
                    break
        if out == -1:
            try:
                out = int(key)
            except ValueError:
                return None
            if not 0 <= out < len(self.contenttypes):
                return None
        return out

    def tolayout(self, lookup, pos, fields):
        if len(fields) > 0:
            index = self.fieldindex(fields[0])
            assert index is not None
            return self.contenttypes[index].tolayout(
                     lookup,
                     lookup.positions[pos + self.CONTENTS + index],
                     fields[1:])
        else:
            contents = []
            for i, contenttype in enumerate(self.contenttypes):
                layout = contenttype.tolayout(
                           lookup,
                           lookup.positions[pos + self.CONTENTS + i],
                           fields)
                contents.append(layout)

            if len(contents) == 0:
                return awkward1.layout.RecordArray(
                         contents,
                         self.recordlookup,
                         numpy.iinfo(numpy.int64).max,
                         parameters=self.parameters)
            else:
                return awkward1.layout.RecordArray(contents,
                                                   self.recordlookup,
                                                   parameters=self.parameters)

    def hasfield(self, key):
        return self.fieldindex(key) is not None

    def getitem_at_check(self, viewtype):
        out = self.getitem_at(viewtype)
        if isinstance(out, awkward1._connect._numba.arrayview.RecordViewType):
            typer = awkward1._util.numba_record_typer(
                      out.arrayviewtype.type, out.arrayviewtype.behavior)
            if typer is not None:
                return typer(out)
        return out

    def getitem_at(self, viewtype):
        if len(viewtype.fields) == 0:
            return awkward1._connect._numba.arrayview.RecordViewType(viewtype)
        else:
            key = viewtype.fields[0]
            index = self.fieldindex(key)
            if index is None:
                if self.recordlookup is None:
                    raise ValueError(
                            "no field {0} in tuples with {1} fields".format(
                              repr(key), len(self.contenttypes)))
                else:
                    raise ValueError(
                            "no field {0} in records with "
                            "fields: [{1}]".format(
                              repr(key),
                              ", ".join(repr(x) for x in self.recordlookup)))
            contenttype = self.contenttypes[index]
            subviewtype = awkward1._connect._numba.arrayview.wrap(
                            contenttype, viewtype, viewtype.fields[1:])
            return contenttype.getitem_at_check(subviewtype)

    def getitem_field(self, viewtype, key):
        index = self.fieldindex(key)
        if index is None:
            if self.recordlookup is None:
                raise ValueError(
                        "no field {0} in tuples with {1} fields".format(
                        repr(key), len(self.contenttypes)))
            else:
                raise ValueError(
                        "no field {0} in records with fields: [{1}]".format(
                          repr(key),
                          ", ".join(repr(x) for x in self.recordlookup)))
        contenttype = self.contenttypes[index]
        subviewtype = awkward1._connect._numba.arrayview.wrap(contenttype,
                                                              viewtype,
                                                              None)
        return contenttype.getitem_range(subviewtype)

    def getitem_field_record(self, recordviewtype, key):
        index = self.fieldindex(key)
        if index is None:
            if self.recordlookup is None:
                raise ValueError(
                        "no field {0} in tuple with {1} fields".format(
                        repr(key), len(self.contenttypes)))
            else:
                raise ValueError(
                        "no field {0} in record with fields: [{1}]".format(
                        repr(key),
                        ", ".join(repr(x) for x in self.recordlookup)))
        contenttype = self.contenttypes[index]
        subviewtype = awkward1._connect._numba.arrayview.wrap(contenttype,
                                                              recordviewtype,
                                                              None)
        return contenttype.getitem_at_check(subviewtype)

    def lower_getitem_at_check(self,
                               context,
                               builder,
                               rettype,
                               viewtype,
                               viewval,
                               viewproxy,
                               attype,
                               atval,
                               wrapneg,
                               checkbounds):
        out = self.lower_getitem_at(context,
                                    builder,
                                    rettype,
                                    viewtype,
                                    viewval,
                                    viewproxy,
                                    attype,
                                    atval,
                                    wrapneg,
                                    checkbounds)
        baretype = self.getitem_at(viewtype)
        if isinstance(baretype,
                      awkward1._connect._numba.arrayview.RecordViewType):
            lower = awkward1._util.numba_record_lower(
                      baretype.arrayviewtype.type,
                      baretype.arrayviewtype.behavior)
            if lower is not None:
                return lower(context, builder, rettype(baretype), (out,))
        return out

    def lower_getitem_at(self,
                         context,
                         builder,
                         rettype,
                         viewtype,
                         viewval,
                         viewproxy,
                         attype,
                         atval,
                         wrapneg,
                         checkbounds):
        atval = regularize_atval(context,
                                 builder,
                                 viewproxy,
                                 attype,
                                 atval,
                                 wrapneg,
                                 checkbounds)

        if len(viewtype.fields) == 0:
            proxyout = context.make_helper(
                         builder,
                         awkward1._connect._numba.arrayview.RecordViewType(
                           viewtype))
            proxyout.arrayview = viewval
            proxyout.at        = atval
            return proxyout._getvalue()

        else:
            index = self.fieldindex(viewtype.fields[0])
            contenttype = self.contenttypes[index]

            whichpos = posat(context,
                             builder,
                             viewproxy.pos,
                             self.CONTENTS + index)
            nextpos = getat(context, builder, viewproxy.arrayptrs, whichpos)

            nextviewtype = awkward1._connect._numba.arrayview.wrap(
                             contenttype, viewtype, viewtype.fields[1:])
            proxynext = context.make_helper(builder, nextviewtype)
            proxynext.pos        = nextpos
            proxynext.start      = viewproxy.start
            proxynext.stop       = builder.add(atval,
                                     builder.add(
                                       viewproxy.start,
                                       context.get_constant(numba.intp, 1)))
            proxynext.arrayptrs  = viewproxy.arrayptrs
            proxynext.sharedptrs = viewproxy.sharedptrs
            proxynext.pylookup   = viewproxy.pylookup

            return contenttype.lower_getitem_at_check(context,
                                                      builder,
                                                      rettype,
                                                      nextviewtype,
                                                      proxynext._getvalue(),
                                                      proxynext,
                                                      numba.intp,
                                                      atval,
                                                      False,
                                                      False)

    def lower_getitem_field(self, context, builder, viewtype, viewval, key):
        viewproxy = context.make_helper(builder, viewtype, viewval)

        index = self.fieldindex(key)
        contenttype = self.contenttypes[index]

        whichpos = posat(context,
                         builder,
                         viewproxy.pos,
                         self.CONTENTS + index)
        nextpos = getat(context, builder, viewproxy.arrayptrs, whichpos)

        proxynext = context.make_helper(builder,
                                        contenttype.getitem_range(viewtype))
        proxynext.pos        = nextpos
        proxynext.start      = viewproxy.start
        proxynext.stop       = viewproxy.stop
        proxynext.arrayptrs  = viewproxy.arrayptrs
        proxynext.sharedptrs = viewproxy.sharedptrs
        proxynext.pylookup   = viewproxy.pylookup

        return proxynext._getvalue()

    def lower_getitem_field_record(self,
                                   context,
                                   builder,
                                   recordviewtype,
                                   recordviewval,
                                   key):
        arrayviewtype = recordviewtype.arrayviewtype
        recordviewproxy = context.make_helper(builder,
                                              recordviewtype,
                                              recordviewval)
        arrayviewval = recordviewproxy.arrayview
        arrayviewproxy = context.make_helper(builder,
                                             arrayviewtype,
                                             arrayviewval)

        index = self.fieldindex(key)
        contenttype = self.contenttypes[index]

        whichpos = posat(context,
                         builder,
                         arrayviewproxy.pos,
                         self.CONTENTS + index)
        nextpos = getat(context, builder, arrayviewproxy.arrayptrs, whichpos)

        proxynext = context.make_helper(
                      builder, contenttype.getitem_range(arrayviewtype))
        proxynext.pos        = nextpos
        proxynext.start      = arrayviewproxy.start
        proxynext.stop       = builder.add(
                                 recordviewproxy.at,
                                 builder.add(
                                   arrayviewproxy.start,
                                   context.get_constant(numba.intp, 1)))
        proxynext.arrayptrs  = arrayviewproxy.arrayptrs
        proxynext.sharedptrs = arrayviewproxy.sharedptrs
        proxynext.pylookup   = arrayviewproxy.pylookup

        nextviewtype = awkward1._connect._numba.arrayview.wrap(contenttype,
                                                               arrayviewtype,
                                                               None)

        rettype = self.getitem_field_record(recordviewtype, key)

        return contenttype.lower_getitem_at_check(context,
                                                  builder,
                                                  rettype,
                                                  nextviewtype,
                                                  proxynext._getvalue(),
                                                  proxynext,
                                                  numba.intp,
                                                  recordviewproxy.at,
                                                  False,
                                                  False)

class UnionArrayType(ContentType):
    IDENTITIES = 0
    TAGS = 1
    INDEX = 2
    CONTENTS = 3

    @classmethod
    def tolookup(cls, layout, positions, sharedptrs, arrays):
        pos = len(positions)
        cls.tolookup_identities(layout, positions, sharedptrs, arrays)
        sharedptrs[-1] = layout._persistent_shared_ptr
        arrays.append(numpy.asarray(layout.tags))
        positions.append(arrays[-1])
        sharedptrs.append(None)
        arrays.append(numpy.asarray(layout.index))
        positions.append(arrays[-1])
        sharedptrs.append(None)
        positions.extend([None] * layout.numcontents)
        sharedptrs.extend([None] * layout.numcontents)
        for i, content in enumerate(layout.contents):
            positions[pos + cls.CONTENTS + i] = \
              awkward1._connect._numba.arrayview.tolookup(content,
                                                          positions,
                                                          sharedptrs,
                                                          arrays)
        return pos

    @classmethod
    def form_tolookup(cls, form, positions, sharedptrs, arrays):
        pos = len(positions)
        cls.form_tolookup_identities(form, positions, sharedptrs, arrays)
        sharedptrs[-1] = 0
        arrays.append(0)
        positions.append(0)
        sharedptrs.append(None)
        arrays.append(0)
        positions.append(0)
        sharedptrs.append(None)
        positions.extend([None] * form.numcontents)
        sharedptrs.extend([None] * form.numcontents)
        for i, content in enumerate(form.contents):
            positions[pos + cls.CONTENTS + i] = \
              awkward1._connect._numba.arrayview.tolookup(content,
                                                          positions,
                                                          sharedptrs,
                                                          arrays)
        return pos

    @classmethod
    def from_form(cls, form):
        contents = []
        for x in form.contents:
            contents.append(
                awkward1._connect._numba.arrayview.tonumbatype(x))

        return UnionArrayType(
            cls.from_form_index(form.tags),
            cls.from_form_index(form.index),
            contents,
            cls.from_form_identities(form),
            form.parameters)

    def __init__(self,
                 tagstype,
                 indextype,
                 contenttypes,
                 identitiestype,
                 parameters):
        super(UnionArrayType, self).__init__(
            name="awkward1.UnionArrayType({0}, {1}, ({2}{3}), {4}, "
            "{5})".format(tagstype.name,
                          indextype.name,
                          ", ".join(x.name for x in contenttypes),
                          "," if len(contenttypes) == 1 else "",
                          identitiestype.name, json.dumps(parameters)))
        self.tagstype = tagstype
        self.indextype = indextype
        self.contenttypes = contenttypes
        self.identitiestype = identitiestype
        self.parameters = parameters

    def form_fill(self, pos, layout, lookup):
        lookup.sharedptrs_hold[pos] = layout._persistent_shared_ptr
        lookup.sharedptrs[pos] = lookup.sharedptrs_hold[pos].ptr()
        self.form_fill_identities(pos, layout, lookup)

        tags = numpy.asarray(layout.tags)
        lookup.original_positions[pos + self.TAGS] = tags
        lookup.arrayptrs[pos + self.TAGS] = tags.ctypes.data

        index = numpy.asarray(layout.index)
        lookup.original_positions[pos + self.INDEX] = index
        lookup.arrayptrs[pos + self.INDEX] = index.ctypes.data

        for i, contenttype in enumerate(self.contenttypes):
            contenttype.form_fill(lookup.arrayptrs[pos + self.CONTENTS + i],
                                  layout.content(i),
                                  lookup)

    def UnionArrayOf(self):
        if self.tagstype.dtype.bitwidth == 8 and self.tagstype.dtype.signed:
            if (self.indextype.dtype.bitwidth == 32 and
                self.indextype.dtype.signed):
                return awkward1.layout.UnionArray8_32
            elif self.indextype.dtype.bitwidth == 32:
                return awkward1.layout.UnionArray8_U32
            elif (self.indextype.dtype.bitwidth == 64 and
                  self.indextype.dtype.signed):
                return awkward1.layout.UnionArray8_64
            else:
                raise AssertionError(
                        "no UnionArray* type for index array: {0}".format(
                          self.indextype))
        else:
            raise AssertionError(
                    "no UnionArray* type for tags array: {0}".format(
                      self.tagstype))

    def tolayout(self, lookup, pos, fields):
        tags = self.IndexOf(self.tagstype)(
                 lookup.original_positions[pos + self.TAGS])
        index = self.IndexOf(self.indextype)(
                  lookup.original_positions[pos + self.INDEX])
        contents = []
        for i, contenttype in enumerate(self.contenttypes):
            layout = contenttype.tolayout(
                       lookup,
                       lookup.positions[pos + self.CONTENTS + i],
                       fields)
            contents.append(layout)
        return self.UnionArrayOf()(tags,
                                   index,
                                   contents,
                                   parameters=self.parameters)

    def hasfield(self, key):
        return any(x.hasfield(key) for x in self.contenttypes)

    def getitem_at(self, viewtype):
        if not all(isinstance(x, RecordArrayType) for x in self.contenttypes):
            raise TypeError("union types cannot be accessed in Numba")

    def getitem_range(self, viewtype):
        if not all(isinstance(x, RecordArrayType) for x in self.contenttypes):
            raise TypeError("union types cannot be accessed in Numba")

    def getitem_field(self, viewtype, key):
        if not all(isinstance(x, RecordArrayType) for x in self.contenttypes):
            raise TypeError("union types cannot be accessed in Numba")

    def lower_getitem_at(self,
                         context,
                         builder,
                         rettype,
                         viewtype,
                         viewval,
                         viewproxy,
                         attype,
                         atval,
                         wrapneg,
                         checkbounds):
        raise NotImplementedError(
            type(self).__name__ + ".lower_getitem_at not implemented")

    def lower_getitem_range(self,
                            context,
                            builder,
                            rettype,
                            viewtype,
                            viewval,
                            viewproxy,
                            start,
                            stop,
                            wrapneg):
        raise NotImplementedError(
            type(self).__name__ + ".lower_getitem_range not implemented")

    def lower_getitem_field(self,
                            context,
                            builder,
                            viewtype,
                            viewval,
                            viewproxy,
                            key):
        raise NotImplementedError(
            type(self).__name__ + ".lower_getitem_field not implemented")

class VirtualArrayType(ContentType):
    IDENTITIES = 0
    PYOBJECT = 1
    ARRAY = 2

    @classmethod
    def tolookup(cls, layout, positions, sharedptrs, arrays):
        pos = len(positions)
        cls.tolookup_identities(layout, positions, sharedptrs, arrays)
        sharedptrs[-1] = layout._persistent_shared_ptr
        if layout.form is None:
            raise ValueError(
                "VirtualArrays without a known 'form' can't be used in Numba")
        pyptr = ctypes.py_object(layout)
        ctypes.pythonapi.Py_IncRef(pyptr)
        voidptr = numpy.frombuffer(pyptr, dtype=numpy.intp).item()

        positions.append(voidptr)
        sharedptrs.append(None)
        positions.append(None)
        sharedptrs.append(None)
        positions[pos + cls.ARRAY] = \
          awkward1._connect._numba.arrayview.tolookup(layout.form.form,
                                                      positions,
                                                      sharedptrs,
                                                      arrays)
        return pos

    @classmethod
    def form_tolookup(cls, form, positions, sharedptrs, arrays):
        pos = len(positions)
        cls.form_tolookup_identities(form, positions, sharedptrs, arrays)
        sharedptrs[-1] = 0
        if form.form is None:
            raise ValueError(
                "VirtualArrays without a known 'form' can't be used in Numba")
        positions.append(0)
        sharedptrs.append(None)
        positions.append(None)
        sharedptrs.append(None)
        positions[pos + cls.ARRAY] = \
          awkward1._connect._numba.arrayview.tolookup(form.form,
                                                      positions,
                                                      sharedptrs,
                                                      arrays)
        return pos

    @classmethod
    def from_form(cls, form):
        if form.form is None:
            raise ValueError(
                "VirtualArrays without a known 'form' can't be used in Numba "
                "(including nested)")
        return VirtualArrayType(form.form,
                                cls.from_form_identities(form),
                                form.parameters)

    def __init__(self, generator_form, identitiestype, parameters):
        if generator_form is None:
            raise ValueError(
                "VirtualArrays without a known 'form' can't be used in Numba")
        super(VirtualArrayType, self).__init__(
            name="awkward1.VirtualArrayType({0}, {1}, {2})".format(
              generator_form.tojson(),
              identitiestype.name,
              json.dumps(parameters)))
        self.generator_form = generator_form
        self.identitiestype = identitiestype
        self.parameters = parameters

    def form_fill(self, pos, layout, lookup):
        lookup.sharedptrs_hold[pos] = layout._persistent_shared_ptr
        lookup.sharedptrs[pos] = lookup.sharedptrs_hold[pos].ptr()
        self.form_fill_identities(pos, layout, lookup)

        pyptr = ctypes.py_object(layout)
        ctypes.pythonapi.Py_IncRef(pyptr)
        voidptr = numpy.frombuffer(pyptr, dtype=numpy.intp).item()

        lookup.original_positions[pos + self.PYOBJECT] = voidptr
        lookup.arrayptrs[pos + self.PYOBJECT] = voidptr

    def tolayout(self, lookup, pos, fields):
        voidptr = ctypes.c_void_p(int(lookup.arrayptrs[pos + self.PYOBJECT]))
        pyptr = ctypes.cast(voidptr, ctypes.py_object)
        ctypes.pythonapi.Py_IncRef(pyptr)
        virtualarray = pyptr.value
        return virtualarray

    def hasfield(self, key):
        return self.generator_form.haskey(key)

    def getitem_at(self, viewtype):
        def getitem_at(form):
            if isinstance(form, awkward1.forms.NumpyForm):
                assert len(form.inner_shape) == 0
                if form.primitive == "float64":
                    return numba.float64
                elif form.primitive == "float32":
                    return numba.float32
                elif form.primitive == "int64":
                    return numba.int64
                elif form.primitive == "uint64":
                    return numba.uint64
                elif form.primitive == "int32":
                    return numba.int32
                elif form.primitive == "uint32":
                    return numba.uint32
                elif form.primitive == "int16":
                    return numba.int16
                elif form.primitive == "uint16":
                    return numba.uint16
                elif form.primitive == "int8":
                    return numba.int8
                elif form.primitive == "uint8":
                    return numba.uint8
                elif form.primitive == "bool":
                    return numba.bool
                else:
                    raise ValueError("unrecognized NumpyForm.primitive type: {0}"
                                     .format(form.primitive))

            elif isinstance(form, (awkward1.forms.RegularForm,
                                   awkward1.forms.ListForm,
                                   awkward1.forms.ListOffsetForm)):
                return form.content

            elif isinstance(form, awkward1.forms.IndexedForm):
                return getitem_at(form.content)

            elif isinstance(form, (awkward1.forms.IndexedOptionForm,
                                   awkward1.forms.ByteMaskedForm,
                                   awkward1.forms.BitMaskedForm,
                                   awkward1.forms.UnmaskedForm)):
                return numba.types.optional(wrap(getitem_at(form.content)))

            elif isinstance(form, awkward1.forms.RecordForm):
                arrayview = wrap(form)
                return arrayview.type.getitem_at(arrayview)

            elif isinstance(form, awkward1.forms.UnionForm):
                raise TypeError("union types cannot be accessed in Numba")

            else:
                raise AssertionError(
                    "unrecognized Form type: {0}".format(type(form)))

        def wrap(out):
            if isinstance(out, awkward1.forms.Form):
                numbatype = awkward1._connect._numba.arrayview.tonumbatype(out)
                return awkward1._connect._numba.arrayview.wrap(numbatype,
                                                               viewtype,
                                                               None)
            else:
                return out

        return wrap(getitem_at(self.generator_form))

    def lower_getitem_at(self,
                         context,
                         builder,
                         rettype,
                         viewtype,
                         viewval,
                         viewproxy,
                         attype,
                         atval,
                         wrapneg,
                         checkbounds):
        pyobjptr = getat(context,
                         builder,
                         viewproxy.arrayptrs,
                         posat(context, builder, viewproxy.pos, self.PYOBJECT))
        arraypos = getat(context,
                         builder,
                         viewproxy.arrayptrs,
                         posat(context, builder, viewproxy.pos, self.ARRAY))
        sharedptr = getat(context, builder, viewproxy.sharedptrs, arraypos)

        numbatype = awkward1._connect._numba.arrayview.tonumbatype(
                        self.generator_form)

        with builder.if_then(builder.icmp_signed(
                                 "==",
                                 sharedptr,
                                 context.get_constant(numba.intp, 0)),
                             likely=False):
            # only rarely enter Python
            pyapi = context.get_python_api(builder)
            gil = pyapi.gil_ensure()

            # borrowed references
            virtualarray_obj = builder.inttoptr(
                                 pyobjptr,
                                 context.get_value_type(numba.types.pyobject))
            lookup_obj = viewproxy.pylookup

            # new references
            numbatype_obj = pyapi.unserialize(pyapi.serialize_object(numbatype))
            fill_obj = pyapi.object_getattr_string(numbatype_obj, "form_fill")
            arraypos_obj = pyapi.long_from_ssize_t(arraypos)
            array_obj = pyapi.object_getattr_string(virtualarray_obj, "array")

            with builder.if_then(builder.icmp_signed(
                                   "!=",
                                   pyapi.err_occurred(),
                                   context.get_constant(numba.types.voidptr,
                                                        0)),
                                 likely=False):
                context.call_conv.return_exc(builder)

            # add the materialized array to our Lookup
            pyapi.call_function_objargs(fill_obj, (arraypos_obj,
                                                   array_obj,
                                                   lookup_obj,))

            with builder.if_then(builder.icmp_signed(
                                   "!=",
                                   pyapi.err_occurred(),
                                   context.get_constant(numba.types.voidptr,
                                                        0)),
                                 likely=False):
                context.call_conv.return_exc(builder)

            # decref the new references
            pyapi.decref(array_obj)
            pyapi.decref(arraypos_obj)
            pyapi.decref(fill_obj)
            pyapi.decref(numbatype_obj)

            pyapi.gil_release(gil)

        # normally, we just pass on the request to the materialized array
        whichpos = posat(context, builder, viewproxy.pos, self.ARRAY)
        nextpos = getat(context, builder, viewproxy.arrayptrs, whichpos)

        nextviewtype = awkward1._connect._numba.arrayview.wrap(
                         numbatype, viewtype, None)
        proxynext = context.make_helper(builder, nextviewtype)
        proxynext.pos        = nextpos
        proxynext.start      = viewproxy.start
        proxynext.stop       = viewproxy.stop
        proxynext.arrayptrs  = viewproxy.arrayptrs
        proxynext.sharedptrs = viewproxy.sharedptrs
        proxynext.pylookup   = viewproxy.pylookup

        return numbatype.lower_getitem_at_check(
                    context,
                    builder,
                    rettype,
                    nextviewtype,
                    proxynext._getvalue(),
                    proxynext,
                    numba.intp,
                    atval,
                    wrapneg,
                    checkbounds)
