def test_bool(self): pyfunc = bool_usecase cr = compile_isolated(pyfunc, [types.Tuple((types.int64, types.int32))]) args = ((4, 5), ) self.assertPreciseEqual(cr.entry_point(*args), pyfunc(*args)) cr = compile_isolated(pyfunc, [types.UniTuple(types.int64, 3)]) args = ((4, 5, 6), ) self.assertPreciseEqual(cr.entry_point(*args), pyfunc(*args)) cr = compile_isolated(pyfunc, [types.Tuple(())]) self.assertPreciseEqual(cr.entry_point(()), pyfunc(()))
def test_add(self): pyfunc = add_usecase samples = [(types.Tuple(()), ()), (types.UniTuple(types.int32, 0), ()), (types.UniTuple(types.int32, 1), (42,)), (types.Tuple((types.int64, types.float32)), (3, 4.5)), ] for (ta, a), (tb, b) in itertools.product(samples, samples): cr = compile_isolated(pyfunc, (ta, tb)) expected = pyfunc(a, b) got = cr.entry_point(a, b) self.assertPreciseEqual(got, expected, msg=(ta, tb))
class Cuda_shfl_sync_intrinsic(ConcreteTemplate): key = cuda.shfl_sync_intrinsic cases = [ signature(types.Tuple((types.i4, types.b1)), types.i4, types.i4, types.i4, types.i4, types.i4), signature(types.Tuple((types.i8, types.b1)), types.i4, types.i4, types.i8, types.i4, types.i4), signature(types.Tuple((types.f4, types.b1)), types.i4, types.i4, types.f4, types.i4, types.i4), signature(types.Tuple((types.f8, types.b1)), types.i4, types.i4, types.f8, types.i4, types.i4), ]
def test_conversions(self): check = self.check_conversion fromty = types.UniTuple(types.int32, 2) check(fromty, types.UniTuple(types.float32, 2), (4, 5)) check(fromty, types.Tuple((types.float32, types.int16)), (4, 5)) aty = types.UniTuple(types.int32, 0) bty = types.Tuple(()) check(aty, bty, ()) check(bty, aty, ()) with self.assertRaises(errors.TypingError) as raises: check(fromty, types.Tuple((types.float32, )), (4, 5)) self.assertIn("No conversion from (int32 x 2) to (float32 x 1)", str(raises.exception))
def test_list(self): aty = types.List(types.undefined) bty = types.List(i32) self.assert_unify(aty, bty, bty) aty = types.List(i16) bty = types.List(i32) self.assert_unify(aty, bty, bty) aty = types.List(types.Tuple([i32, i16])) bty = types.List(types.Tuple([i16, i64])) cty = types.List(types.Tuple([i32, i64])) self.assert_unify(aty, bty, cty) aty = types.List(i16) bty = types.List(types.Tuple([i16])) self.assert_unify_failure(aty, bty)
def _dict_popitem(typingctx, d): """Wrap numba_dict_popitem """ keyvalty = types.Tuple([d.key_type, d.value_type]) resty = types.Tuple([types.int32, types.Optional(keyvalty)]) sig = resty(d) def codegen(context, builder, sig, args): fnty = ir.FunctionType( ll_status, [ll_dict_type, ll_bytes, ll_bytes], ) [d] = args [td] = sig.args fn = builder.module.get_or_insert_function(fnty, name='numba_dict_popitem') dm_key = context.data_model_manager[td.key_type] dm_val = context.data_model_manager[td.value_type] ptr_key = cgutils.alloca_once(builder, dm_key.get_data_type()) ptr_val = cgutils.alloca_once(builder, dm_val.get_data_type()) dp = _dict_get_data(context, builder, td, d) status = builder.call( fn, [ dp, _as_bytes(builder, ptr_key), _as_bytes(builder, ptr_val), ], ) out = context.make_optional_none(builder, keyvalty) pout = cgutils.alloca_once_value(builder, out) cond = builder.icmp_signed('==', status, status.type(int(Status.OK))) with builder.if_then(cond): key = dm_key.load_from_data_pointer(builder, ptr_key) val = dm_val.load_from_data_pointer(builder, ptr_val) keyval = context.make_tuple(builder, keyvalty, [key, val]) optkeyval = context.make_optional_value(builder, keyvalty, keyval) builder.store(optkeyval, pout) out = builder.load(pout) return cgutils.pack_struct(builder, [status, out]) return sig, codegen
def sdc_tuple_map(typingctx, func, data, *args): if not isinstance(func, (types.Dispatcher, types.Function)): assert False, f"sdc_tuple_map's arg 'func' is expected to be " \ f"numba compiled function or a dispatcher, given: {func}" if not isinstance(data, (types.Tuple, types.UniTuple)): assert False, f"sdc_tuple_map's arg 'data' is expected to be a tuple, given: {data}" nargs = len(args) tuple_len = len(data) func_arg_types = [(typ, ) + args for typ in data] ret_tuple_types = [] for i in range(tuple_len): res_sig = func.get_call_type(typingctx, func_arg_types[i], {}) ret_tuple_types.append(res_sig.return_type) ret_type = types.Tuple(ret_tuple_types) ret_sig = ret_type(func, data, types.StarArgTuple.from_types(args)) # codegen below uses first func template to get the dispatcher, so # for now deny compilation for overloaded func-s that have multiple overloads # (using the jitted function dispatcher as func will work anyway) # TO-DO: improve and upstream to Numba if isinstance(func, types.Function): assert len(func.templates) == 1, "Function template has multiple overloads" def codegen(context, builder, sig, args): tup_val = args[1] # main tuple which elements are mapped other_val = [] for i in range(0, nargs): other_val.append( builder.extract_value(args[2], i) ) mapped_values = [] for i in range(tuple_len): tup_elem = builder.extract_value(tup_val, i) input_args = [tup_elem] + other_val call_sig = signature(ret_tuple_types[i], *func_arg_types[i]) if isinstance(func, types.Dispatcher): py_func = func.dispatcher.py_func else: # for function overloads get pyfunc from compiled impl (this # hardcodes the first available template) target_disp = func.templates[0](context.typing_context) py_func = target_disp._get_impl(call_sig.args, {})[0].py_func mapped_values.append( context.compile_internal(builder, py_func, call_sig, input_args) ) res = context.make_tuple(builder, ret_type, mapped_values) return res return ret_sig, codegen
def lower_get_sendrecv_counts(context, builder, sig, args): # prepare buffer args pointer_to_cbuffer_typ = lir.IntType(8).as_pointer().as_pointer() send_counts = cgutils.alloca_once(builder, lir.IntType(8).as_pointer()) recv_counts = cgutils.alloca_once(builder, lir.IntType(8).as_pointer()) send_disp = cgutils.alloca_once(builder, lir.IntType(8).as_pointer()) recv_disp = cgutils.alloca_once(builder, lir.IntType(8).as_pointer()) # prepare key array args key_arr = make_array(sig.args[0])(context, builder, args[0]) # XXX: assuming key arr is 1D assert key_arr.shape.type.count == 1 arr_len = builder.extract_value(key_arr.shape, 0) # TODO: extend to other key types assert sig.args[0].dtype == types.intp key_typ_enum = _h5_typ_table[sig.args[0].dtype] key_typ_arg = builder.load(cgutils.alloca_once_value(builder, lir.Constant(lir.IntType(32), key_typ_enum))) key_arr_data = builder.bitcast(key_arr.data, lir.IntType(8).as_pointer()) call_args = [send_counts, recv_counts, send_disp, recv_disp, arr_len, key_typ_arg, key_arr_data] fnty = lir.FunctionType(lir.IntType(64), [pointer_to_cbuffer_typ] * 4 + [lir.IntType(64), lir.IntType(32), lir.IntType(8).as_pointer()]) fn = builder.module.get_or_insert_function(fnty, name="get_join_sendrecv_counts") total_size = builder.call(fn, call_args) items = [builder.load(send_counts), builder.load(recv_counts), builder.load(send_disp), builder.load(recv_disp), total_size] out_tuple_typ = types.Tuple([c_buffer_type, c_buffer_type, c_buffer_type, c_buffer_type, types.intp]) return context.make_tuple(builder, out_tuple_typ, items)
def if_arr_to_series_type(typ): if isinstance(typ, types.Array) or typ == string_array_type: return arr_to_series_type(typ) if isinstance(typ, (types.Tuple, types.UniTuple)): return types.Tuple([if_arr_to_series_type(t) for t in typ.types]) # TODO: other types than can have Arrays inside: list, set, etc. return typ
def test_tuple_types_exception(self): with self.assertRaises(errors.TypingError) as raises: types.Tuple((types.uint32)) self.assertIn( "Argument 'types' is not iterable", str(raises.exception) )
def test_index(self): pyfunc = tuple_index cr = compile_isolated(pyfunc, [types.UniTuple(types.int64, 3), types.int64]) tup = (4, 3, 6) for i in range(len(tup)): self.assertPreciseEqual(cr.entry_point(tup, i), tup[i]) # Test empty tuple cr = compile_isolated(pyfunc, [types.UniTuple(types.int64, 0), types.int64]) with self.assertRaises(IndexError) as raises: cr.entry_point((), 0) self.assertEqual("tuple index out of range", str(raises.exception)) # With a compile-time static index (the code generation path is different) pyfunc = tuple_index_static for typ in (types.UniTuple(types.int64, 4), types.Tuple((types.int64, types.int32, types.int64, types.int32))): cr = compile_isolated(pyfunc, (typ,)) tup = (4, 3, 42, 6) self.assertPreciseEqual(cr.entry_point(tup), pyfunc(tup)) typ = types.UniTuple(types.int64, 1) with self.assertTypingError(): cr = compile_isolated(pyfunc, (typ,))
def test_lumidata(): from numba import types from numba.typed import Dict lumidata = LumiData("tests/samples/lumi_small.csv") runslumis = np.zeros((10, 2), dtype=np.uint32) runslumis[:, 0] = lumidata._lumidata[0:10, 0] runslumis[:, 1] = lumidata._lumidata[0:10, 1] l = lumidata.get_lumi(runslumis) diff = abs(l - 1.539941814) print("lumi:", l, "diff:", diff) assert (diff < 1e-4) # test build_lumi_table_kernel py_index = Dict.empty(key_type=types.Tuple([types.uint32, types.uint32]), value_type=types.float64) pyruns = lumidata._lumidata[:, 0].astype('u4') pylumis = lumidata._lumidata[:, 1].astype('u4') LumiData.build_lumi_table_kernel.py_func(pyruns, pylumis, lumidata._lumidata, py_index) assert (len(py_index) == len(lumidata.index)) # test get_lumi_kernel py_tot_lumi = np.zeros((1, ), dtype=np.float64) LumiData.get_lumi_kernel.py_func(runslumis[:, 0], runslumis[:, 1], py_index, py_tot_lumi) assert (abs(py_tot_lumi[0] - l) < 1e-4)
def __init__(self, dmm, fe_type): array_types = fe_type.arrays ndim = fe_type.ndim shape_len = ndim if fe_type.need_shaped_indexing else 1 members = [ ('exhausted', types.EphemeralPointer(types.boolean)), ('arrays', types.Tuple(array_types)), # The iterator's main shape and indices ('shape', types.UniTuple(types.intp, shape_len)), ('indices', types.EphemeralArray(types.intp, shape_len)), ] # Indexing state for the various sub-iterators # XXX use a tuple instead? for i, sub in enumerate(fe_type.indexers): kind, start_dim, end_dim, _ = sub member_name = 'index%d' % i if kind == 'flat': # A single index into the flattened array members.append( (member_name, types.EphemeralPointer(types.intp))) elif kind in ('scalar', 'indexed', '0d'): # Nothing required pass else: assert 0 # Slots holding values of the scalar args # XXX use a tuple instead? for i, ty in enumerate(fe_type.arrays): if not isinstance(ty, types.Array): member_name = 'scalar%d' % i members.append((member_name, types.EphemeralPointer(ty))) super(NdIter, self).__init__(dmm, fe_type, members)
def test_len(self): pyfunc = len_usecase cr = compile_isolated(pyfunc, [types.Tuple((types.int64, types.float32))]) self.assertPreciseEqual(cr.entry_point((4, 5)), 2) cr = compile_isolated(pyfunc, [types.UniTuple(types.int64, 3)]) self.assertPreciseEqual(cr.entry_point((4, 5, 6)), 3)
def test_empty_tuples(self): # Empty tuple fe_args = [ types.UniTuple(types.int16, 0), types.Tuple(()), types.int32 ] self._test_as_arguments(fe_args)
def typeof_const(self, inst, target, const): if const is True or const is False: self.typevars[target.name].lock(types.boolean) elif isinstance(const, (int, float)): ty = self.context.get_number_type(const) self.typevars[target.name].lock(ty) elif const is None: self.typevars[target.name].lock(types.none) elif isinstance(const, str): self.typevars[target.name].lock(types.string) elif isinstance(const, complex): self.typevars[target.name].lock(types.complex128) elif isinstance(const, tuple): tys = [] for elem in const: if isinstance(elem, int): tys.append(types.intp) if all(t == types.intp for t in tys): typ = types.UniTuple(types.intp, len(tys)) else: typ = types.Tuple(tys) self.typevars[target.name].lock(typ) else: msg = "Unknown constant of type %s" % (const, ) raise TypingError(msg, loc=inst.loc)
def codegen(context, builder, signature, args): in_tup = args[0] data_arrs = [builder.extract_value(in_tup, i) for i in range(n_cols)] index = builder.extract_value(in_tup, n_cols) column_strs = [ numba.unicode.make_string_from_constant(context, builder, string_type, c) for c in column_names ] # create dataframe struct and store values dataframe = cgutils.create_struct_proxy(signature.return_type)(context, builder) data_tup = context.make_tuple(builder, types.Tuple(data_typs), data_arrs) column_tup = context.make_tuple(builder, types.UniTuple(string_type, n_cols), column_strs) zero = context.get_constant(types.int8, 0) dataframe.data = data_tup dataframe.index = index dataframe.columns = column_tup dataframe.parent = context.get_constant_null(types.pyobject) # increase refcount of stored values if context.enable_nrt: context.nrt.incref(builder, index_typ, index) for var, typ in zip(data_arrs, data_typs): context.nrt.incref(builder, typ, var) for var in column_strs: context.nrt.incref(builder, string_type, var) return dataframe._getvalue()
def depth_map_to_labels(depth_map, geom, labels=None, horizon_idx=0): """ Converts matrix of depths back into dictionary. Can also be used to replace dictionary values with updated ones. Parameters ---------- depth_map : array Matrix of depths. labels : dict, optional If None, then new numba dictionary is created. If dict, then values are written into that dict. horizon_idx : int Index of value to replace in passed `labels`. """ key_type = types.Tuple((types.int64, types.int64)) value_type = types.int64[:] max_count = len(list(labels.values())[0]) if labels else 1 horizon_idx = horizon_idx if labels else 0 labels = labels or Dict.empty(key_type, value_type) @njit def _depth_map_to_labels(depth_map, i_offset, x_offset, labels, horizon_idx, max_count): i_len, x_len = depth_map.shape for il in range(i_len): for xl in range(x_len): key = (il+i_offset, xl+x_offset) value = depth_map[il, xl] if labels.get(key) is None: labels[key] = np.full((max_count, ), FILL_VALUE, np.int64) labels[key][horizon_idx] = value return labels return _depth_map_to_labels(depth_map, geom.ilines_offset, geom.xlines_offset, labels, horizon_idx, max_count)
def hashmap_lookup(typingctx, dict_type, key_type): ty_key, ty_val = dict_type.key_type, dict_type.value_type return_type = types.Tuple([types.bool_, types.Optional(ty_val)]) key_type_postfix, value_type_postfix = _get_types_postfixes(ty_key, ty_val) def codegen(context, builder, sig, args): dict_val, key_val = args key_val, lir_key_type = transform_input_arg(context, builder, key_type, key_val) native_value_ptr, lir_value_type = alloc_native_value( context, builder, ty_val) cdict = cgutils.create_struct_proxy(dict_type)(context, builder, value=dict_val) fnty = lir.FunctionType(lir.IntType(8), [ lir.IntType(8).as_pointer(), lir_key_type, lir_value_type.as_pointer() ]) func_name = f"hashmap_lookup_{key_type_postfix}_to_{value_type_postfix}" fn_hashmap_lookup = cgutils.get_or_insert_function(builder.module, fnty, name=func_name) status = builder.call(fn_hashmap_lookup, [cdict.data_ptr, key_val, native_value_ptr]) status_as_bool = context.cast(builder, status, types.uint8, types.bool_) # if key was not found nothing would be stored to native_value_ptr, so depending on status # we either deref it or not, wrapping final result into types.Optional value result_ptr = cgutils.alloca_once( builder, context.get_value_type(types.Optional(ty_val))) with builder.if_else(status_as_bool, likely=True) as (if_ok, if_not_ok): with if_ok: native_value = builder.load(native_value_ptr) result_value = transform_native_val(context, builder, ty_val, native_value) if context.enable_nrt: context.nrt.incref(builder, ty_val, result_value) builder.store( context.make_optional_value(builder, ty_val, result_value), result_ptr) with if_not_ok: builder.store(context.make_optional_none(builder, ty_val), result_ptr) opt_result = builder.load(result_ptr) return context.make_tuple(builder, return_type, [status_as_bool, opt_result]) func_sig = return_type(dict_type, key_type) return func_sig, codegen
def _list_getitem_pop_helper(typingctx, l, index, op): """Wrap numba_list_getitem and numba_list_pop Returns 2-tuple of (int32, ?item_type) This is a helper that is parametrized on the type of operation, which can be either 'pop' or 'getitem'. This is because, signature wise, getitem and pop and are the same. """ assert (op in ("pop", "getitem")) IS_NOT_NONE = not isinstance(l.item_type, types.NoneType) resty = types.Tuple([ types.int32, types.Optional(l.item_type if IS_NOT_NONE else types.int64) ]) sig = resty(l, index) def codegen(context, builder, sig, args): fnty = ir.FunctionType( ll_status, [ll_list_type, ll_ssize_t, ll_bytes], ) [tl, tindex] = sig.args [l, index] = args fn = builder.module.get_or_insert_function( fnty, name='numba_list_{}'.format(op)) dm_item = context.data_model_manager[tl.item_type] ll_item = context.get_data_type(tl.item_type) ptr_item = cgutils.alloca_once(builder, ll_item) lp = _container_get_data(context, builder, tl, l) status = builder.call( fn, [ lp, index, _as_bytes(builder, ptr_item), ], ) # Load item if output is available found = builder.icmp_signed('>=', status, status.type(int(ListStatus.LIST_OK))) out = context.make_optional_none( builder, tl.item_type if IS_NOT_NONE else types.int64) pout = cgutils.alloca_once_value(builder, out) with builder.if_then(found): if IS_NOT_NONE: item = dm_item.load_from_data_pointer(builder, ptr_item) context.nrt.incref(builder, tl.item_type, item) loaded = context.make_optional_value(builder, tl.item_type, item) builder.store(loaded, pout) out = builder.load(pout) return context.make_tuple(builder, resty, [status, out]) return sig, codegen
def if_series_to_unbox(typ): if isinstance(typ, SeriesType): return UnBoxedSeriesType(typ.dtype) if isinstance(typ, (types.Tuple, types.UniTuple)): return types.Tuple([if_series_to_unbox(t) for t in typ.types]) # TODO: other types than can have Series inside: list, set, etc. return typ
def check_slice(self, pyfunc): tup = (4, 5, 6, 7) cr = compile_isolated(pyfunc, [types.UniTuple(types.int64, 4)]) self.assertPreciseEqual(cr.entry_point(tup), pyfunc(tup)) cr = compile_isolated(pyfunc, [ types.Tuple((types.int64, types.int32, types.int64, types.int32)) ]) self.assertPreciseEqual(cr.entry_point(tup), pyfunc(tup))
def generic(self, args, kws): assert not kws assert len(args) == 1 out_typ = types.Tuple([ c_buffer_type, c_buffer_type, c_buffer_type, c_buffer_type, types.intp ]) return signature(out_typ, *args)
def test_tuple(self): aty = types.UniTuple(i32, 3) bty = types.UniTuple(i64, 3) self.assert_can_convert(aty, aty, Conversion.exact) self.assert_can_convert(aty, bty, Conversion.promote) aty = types.UniTuple(i32, 3) bty = types.UniTuple(f64, 3) self.assert_can_convert(aty, bty, Conversion.safe) aty = types.Tuple((i32, i32)) bty = types.Tuple((i32, i64)) self.assert_can_convert(aty, bty, Conversion.promote) # Failures aty = types.UniTuple(i64, 3) bty = types.UniTuple(types.none, 3) self.assert_cannot_convert(aty, bty) aty = types.UniTuple(i64, 2) bty = types.UniTuple(i64, 3)
def mutate_with_body(self, func_ir, blocks, blk_start, blk_end, body_blocks, dispatcher_factory, extra): typeanns = self._legalize_args(extra, loc=blocks[blk_start].loc) vlt = func_ir.variable_lifetime inputs, outputs = find_region_inout_vars( blocks=blocks, livemap=vlt.livemap, callfrom=blk_start, returnto=blk_end, body_block_ids=set(body_blocks), ) # Determine types in the output tuple def strip_var_ver(x): return x.split('.', 1)[0] stripped_outs = list(map(strip_var_ver, outputs)) # Verify that only outputs are annotated extra_annotated = set(typeanns) - set(stripped_outs) if extra_annotated: msg = ( 'Invalid type annotation on non-outgoing variables: {}.' 'Suggestion: remove annotation of the listed variables' ) raise errors.TypingError(msg.format(extra_annotated)) # Verify that all outputs are annotated not_annotated = set(stripped_outs) - set(typeanns) if not_annotated: msg = 'missing type annotation on outgoing variables: {}' raise errors.TypingError(msg.format(not_annotated)) # Get output types outtup = types.Tuple([typeanns[v] for v in stripped_outs]) lifted_blks = {k: blocks[k] for k in body_blocks} _mutate_with_block_callee(lifted_blks, blk_start, blk_end, inputs, outputs) lifted_ir = func_ir.derive( blocks=lifted_blks, arg_names=tuple(inputs), arg_count=len(inputs), force_non_generator=True, ) dispatcher = dispatcher_factory(lifted_ir, objectmode=True, output_types=outtup) newblk = _mutate_with_block_caller( dispatcher, blocks, blk_start, blk_end, inputs, outputs, ) blocks[blk_start] = newblk _clear_blocks(blocks, body_blocks) return dispatcher
def __call__(self, context, typevars): tsets = [typevars[i.name].get() for i in self.items] oset = typevars[self.target] for vals in itertools.product(*tsets): if all(vals[0] == v for v in vals): tup = types.UniTuple(dtype=vals[0], count=len(vals)) else: tup = types.Tuple(vals) oset.add_types(tup)
def _check_ind_if_hashed(right_keys, r_ind, l_key): if right_keys == types.Tuple((types.intp[::1],)): return lambda right_keys, r_ind, l_key: r_ind def _impl(right_keys, r_ind, l_key): r_key = getitem_arr_tup(right_keys, r_ind) if r_key != l_key: return -1 return r_ind return _impl
def resolve_data_type(self, val): """ Return the numba type of a Python value representing data (e.g. a number or an array, but not more sophisticated types such as functions, etc.) This function can return None to if it cannot decide. """ if val is True or val is False: return types.boolean # Under 2.x, we must guard against numpy scalars (np.intXY # subclasses Python int but get_number_type() wouldn't infer the # right bit width -- perhaps it should?). elif (not isinstance(val, numpy.number) and isinstance(val, utils.INT_TYPES + (float, ))): return self.get_number_type(val) elif val is None: return types.none elif isinstance(val, str): return types.string elif isinstance(val, complex): return types.complex128 elif isinstance(val, tuple): tys = [self.resolve_value_type(v) for v in val] distinct_types = set(tys) if len(distinct_types) == 1: return types.UniTuple(tys[0], len(tys)) else: return types.Tuple(tys) else: try: return numpy_support.map_arrayscalar_type(val) except NotImplementedError: pass if numpy_support.is_array(val): ary = val try: dtype = numpy_support.from_dtype(ary.dtype) except NotImplementedError: return if ary.flags.c_contiguous: layout = 'C' elif ary.flags.f_contiguous: layout = 'F' else: layout = 'A' return types.Array(dtype, ary.ndim, layout) return
def generic(self, args, kws): assert not kws # empty tuple case if len(args) == 0: return signature(types.Tuple(())) (val, ) = args # tuple as input if isinstance(val, types.BaseTuple): return signature(val, val)
def if_arr_to_series_type(typ): if isinstance(typ, (types.Tuple, types.UniTuple)): return types.Tuple([if_arr_to_series_type(t) for t in typ.types]) if isinstance(typ, types.List): return types.List(if_arr_to_series_type(typ.dtype)) if isinstance(typ, types.Set): return types.Set(if_arr_to_series_type(typ.dtype)) # TODO: other types that can have Arrays inside? return typ