1from collections import namedtuple
2from collections.abc import Iterable
3from types import MappingProxyType
4
5from .abstract import (
6    ConstSized,
7    Container,
8    Hashable,
9    MutableSequence,
10    Sequence,
11    Type,
12    TypeRef,
13    Literal,
14    InitialValue,
15    Poison,
16)
17from .common import (
18    Buffer,
19    IterableType,
20    SimpleIterableType,
21    SimpleIteratorType,
22)
23from .misc import Undefined, unliteral, Optional, NoneType
24from ..typeconv import Conversion
25from ..errors import TypingError
26from .. import utils
27
28
29class Pair(Type):
30    """
31    A heterogeneous pair.
32    """
33
34    def __init__(self, first_type, second_type):
35        self.first_type = first_type
36        self.second_type = second_type
37        name = "pair<%s, %s>" % (first_type, second_type)
38        super(Pair, self).__init__(name=name)
39
40    @property
41    def key(self):
42        return self.first_type, self.second_type
43
44    def unify(self, typingctx, other):
45        if isinstance(other, Pair):
46            first = typingctx.unify_pairs(self.first_type, other.first_type)
47            second = typingctx.unify_pairs(self.second_type, other.second_type)
48            if first is not None and second is not None:
49                return Pair(first, second)
50
51
52class BaseContainerIterator(SimpleIteratorType):
53    """
54    Convenience base class for some container iterators.
55
56    Derived classes must implement the *container_class* attribute.
57    """
58
59    def __init__(self, container):
60        assert isinstance(container, self.container_class), container
61        self.container = container
62        yield_type = container.dtype
63        name = "iter(%s)" % container
64        super(BaseContainerIterator, self).__init__(name, yield_type)
65
66    def unify(self, typingctx, other):
67        cls = type(self)
68        if isinstance(other, cls):
69            container = typingctx.unify_pairs(self.container, other.container)
70            if container is not None:
71                return cls(container)
72
73    @property
74    def key(self):
75        return self.container
76
77
78class BaseContainerPayload(Type):
79    """
80    Convenience base class for some container payloads.
81
82    Derived classes must implement the *container_class* attribute.
83    """
84
85    def __init__(self, container):
86        assert isinstance(container, self.container_class)
87        self.container = container
88        name = "payload(%s)" % container
89        super(BaseContainerPayload, self).__init__(name)
90
91    @property
92    def key(self):
93        return self.container
94
95
96class Bytes(Buffer):
97    """
98    Type class for Python 3.x bytes objects.
99    """
100
101    mutable = False
102    # Actually true but doesn't matter since bytes is immutable
103    slice_is_copy = False
104
105
106class ByteArray(Buffer):
107    """
108    Type class for bytearray objects.
109    """
110
111    slice_is_copy = True
112
113
114class PyArray(Buffer):
115    """
116    Type class for array.array objects.
117    """
118
119    slice_is_copy = True
120
121
122class MemoryView(Buffer):
123    """
124    Type class for memoryview objects.
125    """
126
127
128def is_homogeneous(*tys):
129    """Are the types homogeneous?
130    """
131    if tys:
132        first, tys = tys[0], tys[1:]
133        return not any(t != first for t in tys)
134    else:
135        # *tys* is empty.
136        return False
137
138
139class BaseTuple(ConstSized, Hashable):
140    """
141    The base class for all tuple types (with a known size).
142    """
143
144    @classmethod
145    def from_types(cls, tys, pyclass=None):
146        """
147        Instantiate the right tuple type for the given element types.
148        """
149        if pyclass is not None and pyclass is not tuple:
150            # A subclass => is it a namedtuple?
151            assert issubclass(pyclass, tuple)
152            if hasattr(pyclass, "_asdict"):
153                tys = tuple(map(unliteral, tys))
154                homogeneous = is_homogeneous(*tys)
155                if homogeneous:
156                    return NamedUniTuple(tys[0], len(tys), pyclass)
157                else:
158                    return NamedTuple(tys, pyclass)
159        else:
160            dtype = utils.unified_function_type(tys)
161            if dtype is not None:
162                return UniTuple(dtype, len(tys))
163            # non-named tuple
164            homogeneous = is_homogeneous(*tys)
165            if homogeneous:
166                return cls._make_homogeneous_tuple(tys[0], len(tys))
167            else:
168                return cls._make_heterogeneous_tuple(tys)
169
170    @classmethod
171    def _make_homogeneous_tuple(cls, dtype, count):
172        return UniTuple(dtype, count)
173
174    @classmethod
175    def _make_heterogeneous_tuple(cls, tys):
176        return Tuple(tys)
177
178
179class BaseAnonymousTuple(BaseTuple):
180    """
181    Mixin for non-named tuples.
182    """
183
184    def can_convert_to(self, typingctx, other):
185        """
186        Convert this tuple to another one.  Note named tuples are rejected.
187        """
188        if not isinstance(other, BaseAnonymousTuple):
189            return
190        if len(self) != len(other):
191            return
192        if len(self) == 0:
193            return Conversion.safe
194        if isinstance(other, BaseTuple):
195            kinds = [
196                typingctx.can_convert(ta, tb) for ta, tb in zip(self, other)
197            ]
198            if any(kind is None for kind in kinds):
199                return
200            return max(kinds)
201
202    def __unliteral__(self):
203        return type(self).from_types([unliteral(t) for t in self])
204
205
206class _HomogeneousTuple(Sequence, BaseTuple):
207    @property
208    def iterator_type(self):
209        return UniTupleIter(self)
210
211    def __getitem__(self, i):
212        """
213        Return element at position i
214        """
215        return self.dtype
216
217    def __iter__(self):
218        return iter([self.dtype] * self.count)
219
220    def __len__(self):
221        return self.count
222
223    @property
224    def types(self):
225        return (self.dtype,) * self.count
226
227
228class UniTuple(BaseAnonymousTuple, _HomogeneousTuple, Sequence):
229    """
230    Type class for homogeneous tuples.
231    """
232
233    def __init__(self, dtype, count):
234        self.dtype = dtype
235        self.count = count
236        name = "%s(%s x %d)" % (self.__class__.__name__, dtype, count,)
237        super(UniTuple, self).__init__(name)
238
239    @property
240    def mangling_args(self):
241        return self.__class__.__name__, (self.dtype, self.count)
242
243    @property
244    def key(self):
245        return self.dtype, self.count
246
247    def unify(self, typingctx, other):
248        """
249        Unify UniTuples with their dtype
250        """
251        if isinstance(other, UniTuple) and len(self) == len(other):
252            dtype = typingctx.unify_pairs(self.dtype, other.dtype)
253            if dtype is not None:
254                return UniTuple(dtype=dtype, count=self.count)
255
256    def __unliteral__(self):
257        return type(self)(dtype=unliteral(self.dtype), count=self.count)
258
259
260class UniTupleIter(BaseContainerIterator):
261    """
262    Type class for homogeneous tuple iterators.
263    """
264
265    container_class = _HomogeneousTuple
266
267
268class _HeterogeneousTuple(BaseTuple):
269    def __getitem__(self, i):
270        """
271        Return element at position i
272        """
273        return self.types[i]
274
275    def __len__(self):
276        # Beware: this makes Tuple(()) false-ish
277        return len(self.types)
278
279    def __iter__(self):
280        return iter(self.types)
281
282    @staticmethod
283    def is_types_iterable(types):
284        # issue 4463 - check if argument 'types' is iterable
285        if not isinstance(types, Iterable):
286            raise TypingError("Argument 'types' is not iterable")
287
288
289class UnionType(Type):
290    def __init__(self, types):
291        self.types = tuple(sorted(set(types), key=lambda x: x.name))
292        name = "Union[{}]".format(",".join(map(str, self.types)))
293        super(UnionType, self).__init__(name=name)
294
295    def get_type_tag(self, typ):
296        return self.types.index(typ)
297
298
299class Tuple(BaseAnonymousTuple, _HeterogeneousTuple):
300    def __new__(cls, types):
301
302        t = utils.unified_function_type(types, require_precise=True)
303        if t is not None:
304            return UniTuple(dtype=t, count=len(types))
305
306        _HeterogeneousTuple.is_types_iterable(types)
307
308        if types and all(t == types[0] for t in types[1:]):
309            return UniTuple(dtype=types[0], count=len(types))
310        else:
311            return object.__new__(Tuple)
312
313    def __init__(self, types):
314        self.types = tuple(types)
315        self.count = len(self.types)
316        self.dtype = UnionType(types)
317        name = "%s(%s)" % (
318            self.__class__.__name__,
319            ", ".join(str(i) for i in self.types),
320        )
321        super(Tuple, self).__init__(name)
322
323    @property
324    def mangling_args(self):
325        return self.__class__.__name__, tuple(t for t in self.types)
326
327    @property
328    def key(self):
329        return self.types
330
331    def unify(self, typingctx, other):
332        """
333        Unify elements of Tuples/UniTuples
334        """
335        # Other is UniTuple or Tuple
336        if isinstance(other, BaseTuple) and len(self) == len(other):
337            unified = [
338                typingctx.unify_pairs(ta, tb) for ta, tb in zip(self, other)
339            ]
340
341            if all(t is not None for t in unified):
342                return Tuple(unified)
343
344
345class _StarArgTupleMixin:
346    @classmethod
347    def _make_homogeneous_tuple(cls, dtype, count):
348        return StarArgUniTuple(dtype, count)
349
350    @classmethod
351    def _make_heterogeneous_tuple(cls, tys):
352        return StarArgTuple(tys)
353
354
355class StarArgTuple(_StarArgTupleMixin, Tuple):
356    """To distinguish from Tuple() used as argument to a `*args`.
357    """
358
359    def __new__(cls, types):
360        _HeterogeneousTuple.is_types_iterable(types)
361
362        if types and all(t == types[0] for t in types[1:]):
363            return StarArgUniTuple(dtype=types[0], count=len(types))
364        else:
365            return object.__new__(StarArgTuple)
366
367
368class StarArgUniTuple(_StarArgTupleMixin, UniTuple):
369    """To distinguish from UniTuple() used as argument to a `*args`.
370    """
371
372
373class BaseNamedTuple(BaseTuple):
374    pass
375
376
377class NamedUniTuple(_HomogeneousTuple, BaseNamedTuple):
378    def __init__(self, dtype, count, cls):
379        self.dtype = dtype
380        self.count = count
381        self.fields = tuple(cls._fields)
382        self.instance_class = cls
383        name = "%s(%s x %d)" % (cls.__name__, dtype, count)
384        super(NamedUniTuple, self).__init__(name)
385
386    @property
387    def iterator_type(self):
388        return UniTupleIter(self)
389
390    @property
391    def key(self):
392        return self.instance_class, self.dtype, self.count
393
394
395class NamedTuple(_HeterogeneousTuple, BaseNamedTuple):
396    def __init__(self, types, cls):
397        _HeterogeneousTuple.is_types_iterable(types)
398
399        self.types = tuple(types)
400        self.count = len(self.types)
401        self.fields = tuple(cls._fields)
402        self.instance_class = cls
403        name = "%s(%s)" % (cls.__name__, ", ".join(str(i) for i in self.types))
404        super(NamedTuple, self).__init__(name)
405
406    @property
407    def key(self):
408        return self.instance_class, self.types
409
410
411class List(MutableSequence, InitialValue):
412    """
413    Type class for (arbitrary-sized) homogeneous lists.
414    """
415
416    mutable = True
417
418    def __init__(self, dtype, reflected=False, initial_value=None):
419        dtype = unliteral(dtype)
420        self.dtype = dtype
421        self.reflected = reflected
422        cls_name = "reflected list" if reflected else "list"
423        name = "%s(%s)<iv=%s>" % (cls_name, self.dtype, initial_value)
424        super(List, self).__init__(name=name)
425        InitialValue.__init__(self, initial_value)
426
427    def copy(self, dtype=None, reflected=None):
428        if dtype is None:
429            dtype = self.dtype
430        if reflected is None:
431            reflected = self.reflected
432        return List(dtype, reflected, self.initial_value)
433
434    def unify(self, typingctx, other):
435        if isinstance(other, List):
436            dtype = typingctx.unify_pairs(self.dtype, other.dtype)
437            reflected = self.reflected or other.reflected
438            if dtype is not None:
439                siv = self.initial_value
440                oiv = other.initial_value
441                if siv is not None and oiv is not None:
442                    use = siv
443                    if siv is None:
444                        use = oiv
445                    return List(dtype, reflected, use)
446                else:
447                    return List(dtype, reflected)
448
449    @property
450    def key(self):
451        return self.dtype, self.reflected, str(self.initial_value)
452
453    @property
454    def iterator_type(self):
455        return ListIter(self)
456
457    def is_precise(self):
458        return self.dtype.is_precise()
459
460    def __getitem__(self, args):
461        """
462        Overrides the default __getitem__ from Type.
463        """
464        return self.dtype
465
466    def __unliteral__(self):
467        return List(self.dtype, reflected=self.reflected,
468                    initial_value=None)
469
470
471class LiteralList(Literal, ConstSized, Hashable):
472    """A heterogeneous immutable list (basically a tuple with list semantics).
473    """
474
475    mutable = False
476
477    def __init__(self, literal_value):
478        self.is_types_iterable(literal_value)
479        self._literal_init(list(literal_value))
480        self.types = tuple(literal_value)
481        self.count = len(self.types)
482        self.name = "LiteralList({})".format(literal_value)
483
484    def __getitem__(self, i):
485        """
486        Return element at position i
487        """
488        return self.types[i]
489
490    def __len__(self):
491        return len(self.types)
492
493    def __iter__(self):
494        return iter(self.types)
495
496    @classmethod
497    def from_types(cls, tys):
498        return LiteralList(tys)
499
500    @staticmethod
501    def is_types_iterable(types):
502        if not isinstance(types, Iterable):
503            raise TypingError("Argument 'types' is not iterable")
504
505    @property
506    def iterator_type(self):
507        return ListIter(self)
508
509    def __unliteral__(self):
510        return Poison(self)
511
512    def unify(self, typingctx, other):
513        """
514        Unify this with the *other* one.
515        """
516        if isinstance(other, LiteralList) and self.count == other.count:
517            tys = []
518            for i1, i2 in zip(self.types, other.types):
519                tys.append(typingctx.unify_pairs(i1, i2))
520            if all(tys):
521                return LiteralList(tys)
522
523
524class ListIter(BaseContainerIterator):
525    """
526    Type class for list iterators.
527    """
528
529    container_class = List
530
531
532class ListPayload(BaseContainerPayload):
533    """
534    Internal type class for the dynamically-allocated payload of a list.
535    """
536
537    container_class = List
538
539
540class Set(Container):
541    """
542    Type class for homogeneous sets.
543    """
544
545    mutable = True
546
547    def __init__(self, dtype, reflected=False):
548        assert isinstance(dtype, (Hashable, Undefined))
549        self.dtype = dtype
550        self.reflected = reflected
551        cls_name = "reflected set" if reflected else "set"
552        name = "%s(%s)" % (cls_name, self.dtype)
553        super(Set, self).__init__(name=name)
554
555    @property
556    def key(self):
557        return self.dtype, self.reflected
558
559    @property
560    def iterator_type(self):
561        return SetIter(self)
562
563    def is_precise(self):
564        return self.dtype.is_precise()
565
566    def copy(self, dtype=None, reflected=None):
567        if dtype is None:
568            dtype = self.dtype
569        if reflected is None:
570            reflected = self.reflected
571        return Set(dtype, reflected)
572
573    def unify(self, typingctx, other):
574        if isinstance(other, Set):
575            dtype = typingctx.unify_pairs(self.dtype, other.dtype)
576            reflected = self.reflected or other.reflected
577            if dtype is not None:
578                return Set(dtype, reflected)
579
580
581class SetIter(BaseContainerIterator):
582    """
583    Type class for set iterators.
584    """
585
586    container_class = Set
587
588
589class SetPayload(BaseContainerPayload):
590    """
591    Internal type class for the dynamically-allocated payload of a set.
592    """
593
594    container_class = Set
595
596
597class SetEntry(Type):
598    """
599    Internal type class for the entries of a Set's hash table.
600    """
601
602    def __init__(self, set_type):
603        self.set_type = set_type
604        name = "entry(%s)" % set_type
605        super(SetEntry, self).__init__(name)
606
607    @property
608    def key(self):
609        return self.set_type
610
611
612class ListType(IterableType):
613    """List type
614    """
615
616    mutable = True
617
618    def __init__(self, itemty):
619        assert not isinstance(itemty, TypeRef)
620        itemty = unliteral(itemty)
621        if isinstance(itemty, Optional):
622            fmt = "List.item_type cannot be of type {}"
623            raise TypingError(fmt.format(itemty))
624        # FIXME: _sentry_forbidden_types(itemty)
625        self.item_type = itemty
626        self.dtype = itemty
627        name = "{}[{}]".format(self.__class__.__name__, itemty,)
628        super(ListType, self).__init__(name)
629
630    def is_precise(self):
631        return not isinstance(self.item_type, Undefined)
632
633    @property
634    def iterator_type(self):
635        return ListTypeIterableType(self).iterator_type
636
637    @classmethod
638    def refine(cls, itemty):
639        """Refine to a precise list type
640        """
641        res = cls(itemty)
642        assert res.is_precise()
643        return res
644
645    def unify(self, typingctx, other):
646        """
647        Unify this with the *other* list.
648        """
649        # If other is list
650        if isinstance(other, ListType):
651            if not other.is_precise():
652                return self
653
654
655class ListTypeIterableType(SimpleIterableType):
656    """List iterable type
657    """
658
659    def __init__(self, parent):
660        assert isinstance(parent, ListType)
661        self.parent = parent
662        self.yield_type = self.parent.item_type
663        name = "list[{}]".format(self.parent.name)
664        iterator_type = ListTypeIteratorType(self)
665        super(ListTypeIterableType, self).__init__(name, iterator_type)
666
667
668class ListTypeIteratorType(SimpleIteratorType):
669    def __init__(self, iterable):
670        self.parent = iterable.parent
671        self.iterable = iterable
672        yield_type = iterable.yield_type
673        name = "iter[{}->{}]".format(iterable.parent, yield_type)
674        super(ListTypeIteratorType, self).__init__(name, yield_type)
675
676
677def _sentry_forbidden_types(key, value):
678    # Forbids List and Set for now
679    if isinstance(key, (Set, List)):
680        raise TypingError("{} as key is forbidden".format(key))
681    if isinstance(value, (Set, List)):
682        raise TypingError("{} as value is forbidden".format(value))
683
684
685class DictType(IterableType, InitialValue):
686    """Dictionary type
687    """
688
689    def __init__(self, keyty, valty, initial_value=None):
690        assert not isinstance(keyty, TypeRef)
691        assert not isinstance(valty, TypeRef)
692        keyty = unliteral(keyty)
693        valty = unliteral(valty)
694        if isinstance(keyty, (Optional, NoneType)):
695            fmt = "Dict.key_type cannot be of type {}"
696            raise TypingError(fmt.format(keyty))
697        if isinstance(valty, (Optional, NoneType)):
698            fmt = "Dict.value_type cannot be of type {}"
699            raise TypingError(fmt.format(valty))
700        _sentry_forbidden_types(keyty, valty)
701        self.key_type = keyty
702        self.value_type = valty
703        self.keyvalue_type = Tuple([keyty, valty])
704        name = "{}[{},{}]<iv={}>".format(
705            self.__class__.__name__, keyty, valty, initial_value
706        )
707        super(DictType, self).__init__(name)
708        InitialValue.__init__(self, initial_value)
709
710    def is_precise(self):
711        return not any(
712            (
713                isinstance(self.key_type, Undefined),
714                isinstance(self.value_type, Undefined),
715            )
716        )
717
718    @property
719    def iterator_type(self):
720        return DictKeysIterableType(self).iterator_type
721
722    @classmethod
723    def refine(cls, keyty, valty):
724        """Refine to a precise dictionary type
725        """
726        res = cls(keyty, valty)
727        assert res.is_precise()
728        return res
729
730    def unify(self, typingctx, other):
731        """
732        Unify this with the *other* dictionary.
733        """
734        # If other is dict
735        if isinstance(other, DictType):
736            if not other.is_precise():
737                return self
738            else:
739                ukey_type = self.key_type == other.key_type
740                uvalue_type = self.value_type == other.value_type
741                if ukey_type and uvalue_type:
742                    siv = self.initial_value
743                    oiv = other.initial_value
744                    siv_none = siv is None
745                    oiv_none = oiv is None
746                    if not siv_none and not oiv_none:
747                        if siv == oiv:
748                            return DictType(self.key_type, other.value_type,
749                                            siv)
750                    return DictType(self.key_type, other.value_type)
751
752    @property
753    def key(self):
754        return self.key_type, self.value_type, str(self.initial_value)
755
756    def __unliteral__(self):
757        return DictType(self.key_type, self.value_type)
758
759
760class LiteralStrKeyDict(Literal, ConstSized, Hashable):
761    """A Dictionary of string keys to heterogeneous values (basically a
762    namedtuple with dict semantics).
763    """
764
765    mutable = False
766
767    def __init__(self, literal_value, value_index=None):
768        self._literal_init(literal_value)
769        self.value_index = value_index
770        strkeys = [x.literal_value for x in literal_value.keys()]
771        self.tuple_ty = namedtuple("_ntclazz", " ".join(strkeys))
772        tys = [x for x in literal_value.values()]
773        self.types = tuple(tys)
774        self.count = len(self.types)
775        self.fields = tuple(self.tuple_ty._fields)
776        self.instance_class = self.tuple_ty
777        self.name = "LiteralStrKey[Dict]({})".format(literal_value)
778
779    def __unliteral__(self):
780        return Poison(self)
781
782    def unify(self, typingctx, other):
783        """
784        Unify this with the *other* one.
785        """
786        if isinstance(other, LiteralStrKeyDict):
787            tys = []
788            for (k1, v1), (k2, v2) in zip(
789                self.literal_value.items(), other.literal_value.items()
790            ):
791                if k1 != k2:  # keys must be same
792                    break
793                tys.append(typingctx.unify_pairs(v1, v2))
794            else:
795                if all(tys):
796                    d = {k: v for k, v in zip(self.literal_value.keys(), tys)}
797                    return LiteralStrKeyDict(d)
798
799    def __len__(self):
800        return len(self.types)
801
802    def __iter__(self):
803        return iter(self.types)
804
805    @property
806    def key(self):
807        # use the namedtuple fields not the namedtuple itself as it's created
808        # locally in the ctor and comparison would always be False.
809        return self.tuple_ty._fields, self.types, str(self.literal_value)
810
811
812class DictItemsIterableType(SimpleIterableType):
813    """Dictionary iterable type for .items()
814    """
815
816    def __init__(self, parent):
817        assert isinstance(parent, DictType)
818        self.parent = parent
819        self.yield_type = self.parent.keyvalue_type
820        name = "items[{}]".format(self.parent.name)
821        self.name = name
822        iterator_type = DictIteratorType(self)
823        super(DictItemsIterableType, self).__init__(name, iterator_type)
824
825
826class DictKeysIterableType(SimpleIterableType):
827    """Dictionary iterable type for .keys()
828    """
829
830    def __init__(self, parent):
831        assert isinstance(parent, DictType)
832        self.parent = parent
833        self.yield_type = self.parent.key_type
834        name = "keys[{}]".format(self.parent.name)
835        self.name = name
836        iterator_type = DictIteratorType(self)
837        super(DictKeysIterableType, self).__init__(name, iterator_type)
838
839
840class DictValuesIterableType(SimpleIterableType):
841    """Dictionary iterable type for .values()
842    """
843
844    def __init__(self, parent):
845        assert isinstance(parent, DictType)
846        self.parent = parent
847        self.yield_type = self.parent.value_type
848        name = "values[{}]".format(self.parent.name)
849        self.name = name
850        iterator_type = DictIteratorType(self)
851        super(DictValuesIterableType, self).__init__(name, iterator_type)
852
853
854class DictIteratorType(SimpleIteratorType):
855    def __init__(self, iterable):
856        self.parent = iterable.parent
857        self.iterable = iterable
858        yield_type = iterable.yield_type
859        name = "iter[{}->{}],{}".format(
860            iterable.parent, yield_type, iterable.name
861        )
862        super(DictIteratorType, self).__init__(name, yield_type)
863
864
865class StructRef(Type):
866    """A mutable struct.
867    """
868
869    def __init__(self, fields):
870        """
871        Parameters
872        ----------
873        fields : Sequence
874            A sequence of field descriptions, which is a 2-tuple-like object
875            containing `(name, type)`, where `name` is a `str` for the field
876            name, and `type` is a numba type for the field type.
877        """
878
879        def check_field_pair(fieldpair):
880            name, typ = fieldpair
881            if not isinstance(name, str):
882                msg = "expecting a str for field name"
883                raise ValueError(msg)
884            if not isinstance(typ, Type):
885                msg = "expecting a Numba Type for field type"
886                raise ValueError(msg)
887            return name, typ
888
889        fields = tuple(map(check_field_pair, fields))
890        self._fields = tuple(map(check_field_pair,
891                                 self.preprocess_fields(fields)))
892        self._typename = self.__class__.__qualname__
893        name = f"numba.{self._typename}{self._fields}"
894        super().__init__(name=name)
895
896    def preprocess_fields(self, fields):
897        """Subclasses can override this to do additional clean up on fields.
898
899        The default is an identity function.
900
901        Parameters:
902        -----------
903        fields : Sequence[Tuple[str, Type]]
904        """
905        return fields
906
907    @property
908    def field_dict(self):
909        """Return an immutable mapping for the field names and their
910        corresponding types.
911        """
912        return MappingProxyType(dict(self._fields))
913
914    def get_data_type(self):
915        """Get the payload type for the actual underlying structure referred
916        to by this struct reference.
917
918        See also: `ClassInstanceType.get_data_type`
919        """
920        return StructRefPayload(
921            typename=self.__class__.__name__, fields=self._fields,
922        )
923
924
925class StructRefPayload(Type):
926    """The type of the payload of a mutable struct.
927    """
928
929    mutable = True
930
931    def __init__(self, typename, fields):
932        self._typename = typename
933        self._fields = tuple(fields)
934        super().__init__(name=f"numba.{typename}{self._fields}.payload")
935
936    @property
937    def field_dict(self):
938        return MappingProxyType(dict(self._fields))
939