1"""
2Numba-specific errors and warnings.
3"""
4
5
6import abc
7import contextlib
8import os
9import sys
10import warnings
11import numba.core.config
12import numpy as np
13from collections import defaultdict
14from numba.core.utils import add_metaclass, reraise, chain_exception
15from functools import wraps
16from abc import abstractmethod
17
18# Filled at the end
19__all__ = []
20
21
22class NumbaWarning(Warning):
23    """
24    Base category for all Numba compiler warnings.
25    """
26
27    def __init__(self, msg, loc=None, highlighting=True, ):
28        self.msg = msg
29        self.loc = loc
30        if highlighting:
31            highlight = termcolor().errmsg
32        else:
33            def highlight(x):
34                return x
35        if loc:
36            super(NumbaWarning, self).__init__(
37                highlight("%s\n%s\n" % (msg, loc.strformat())))
38        else:
39            super(NumbaWarning, self).__init__(highlight("%s" % (msg,)))
40
41
42class NumbaPerformanceWarning(NumbaWarning):
43    """
44    Warning category for when an operation might not be
45    as fast as expected.
46    """
47
48
49class NumbaDeprecationWarning(NumbaWarning):
50    """
51    Warning category for use of a deprecated feature.
52    """
53
54
55class NumbaPendingDeprecationWarning(NumbaWarning):
56    """
57    Warning category for use of a feature that is pending deprecation.
58    """
59
60
61class NumbaParallelSafetyWarning(NumbaWarning):
62    """
63    Warning category for when an operation in a prange
64    might not have parallel semantics.
65    """
66
67
68class NumbaTypeSafetyWarning(NumbaWarning):
69    """
70    Warning category for unsafe casting operations.
71    """
72
73
74class NumbaExperimentalFeatureWarning(NumbaWarning):
75    """
76    Warning category for using an experimental feature.
77    """
78
79# These are needed in the color formatting of errors setup
80
81
82@add_metaclass(abc.ABCMeta)
83class _ColorScheme(object):
84
85    @abstractmethod
86    def code(self, msg):
87        pass
88
89    @abstractmethod
90    def errmsg(self, msg):
91        pass
92
93    @abstractmethod
94    def filename(self, msg):
95        pass
96
97    @abstractmethod
98    def indicate(self, msg):
99        pass
100
101    @abstractmethod
102    def highlight(self, msg):
103        pass
104
105    @abstractmethod
106    def reset(self, msg):
107        pass
108
109
110class _DummyColorScheme(_ColorScheme):
111
112    def __init__(self, theme=None):
113        pass
114
115    def code(self, msg):
116        pass
117
118    def errmsg(self, msg):
119        pass
120
121    def filename(self, msg):
122        pass
123
124    def indicate(self, msg):
125        pass
126
127    def highlight(self, msg):
128        pass
129
130    def reset(self, msg):
131        pass
132
133
134# holds reference to the instance of the terminal color scheme in use
135_termcolor_inst = None
136
137try:
138    import colorama
139
140    # If the colorama version is < 0.3.9 it can break stdout/stderr in some
141    # situations, as a result if this condition is met colorama is disabled and
142    # the user is warned. Note that early versions did not have a __version__.
143    colorama_version = getattr(colorama, '__version__', '0.0.0')
144
145    if tuple([int(x) for x in colorama_version.split('.')]) < (0, 3, 9):
146        msg = ("Insufficiently recent colorama version found. "
147               "Numba requires colorama >= 0.3.9")
148        # warn the user
149        warnings.warn(msg)
150        # trip the exception to disable color errors
151        raise ImportError
152
153    # If Numba is running in testsuite mode then do not use error message
154    # coloring so CI system output is consistently readable without having
155    # to read between shell escape characters.
156    if os.environ.get('NUMBA_DISABLE_ERROR_MESSAGE_HIGHLIGHTING', None):
157        raise ImportError  # just to trigger the exception handler below
158
159except ImportError:
160
161    class NOPColorScheme(_DummyColorScheme):
162        def __init__(self, theme=None):
163            if theme is not None:
164                raise ValueError("specifying a theme has no effect")
165            _DummyColorScheme.__init__(self, theme=theme)
166
167        def code(self, msg):
168            return msg
169
170        def errmsg(self, msg):
171            return msg
172
173        def filename(self, msg):
174            return msg
175
176        def indicate(self, msg):
177            return msg
178
179        def highlight(self, msg):
180            return msg
181
182        def reset(self, msg):
183            return msg
184
185    def termcolor():
186        global _termcolor_inst
187        if _termcolor_inst is None:
188            _termcolor_inst = NOPColorScheme()
189        return _termcolor_inst
190
191else:
192
193    from colorama import init, reinit, deinit, Fore, Style
194
195    class ColorShell(object):
196        _has_initialized = False
197
198        def __init__(self):
199            init()
200            self._has_initialized = True
201
202        def __enter__(self):
203            if self._has_initialized:
204                reinit()
205
206        def __exit__(self, *exc_detail):
207            Style.RESET_ALL
208            deinit()
209
210    class reset_terminal(object):
211        def __init__(self):
212            self._buf = bytearray(b'')
213
214        def __enter__(self):
215            return self._buf
216
217        def __exit__(self, *exc_detail):
218            self._buf += bytearray(Style.RESET_ALL.encode('utf-8'))
219
220    # define some default themes, if more are added, update the envvars docs!
221    themes = {}
222
223    # No color added, just bold weighting
224    themes['no_color'] = {'code': None,
225                          'errmsg': None,
226                          'filename': None,
227                          'indicate': None,
228                          'highlight': None,
229                          'reset': None, }
230
231    # suitable for terminals with a dark background
232    themes['dark_bg'] = {'code': Fore.BLUE,
233                         'errmsg': Fore.YELLOW,
234                         'filename': Fore.WHITE,
235                         'indicate': Fore.GREEN,
236                         'highlight': Fore.RED,
237                         'reset': Style.RESET_ALL, }
238
239    # suitable for terminals with a light background
240    themes['light_bg'] = {'code': Fore.BLUE,
241                          'errmsg': Fore.BLACK,
242                          'filename': Fore.MAGENTA,
243                          'indicate': Fore.BLACK,
244                          'highlight': Fore.RED,
245                          'reset': Style.RESET_ALL, }
246
247    # suitable for terminals with a blue background
248    themes['blue_bg'] = {'code': Fore.WHITE,
249                         'errmsg': Fore.YELLOW,
250                         'filename': Fore.MAGENTA,
251                         'indicate': Fore.CYAN,
252                         'highlight': Fore.RED,
253                         'reset': Style.RESET_ALL, }
254
255    # suitable for use in jupyter notebooks
256    themes['jupyter_nb'] = {'code': Fore.BLACK,
257                            'errmsg': Fore.BLACK,
258                            'filename': Fore.GREEN,
259                            'indicate': Fore.CYAN,
260                            'highlight': Fore.RED,
261                            'reset': Style.RESET_ALL, }
262
263    default_theme = themes['no_color']
264
265    class HighlightColorScheme(_DummyColorScheme):
266        def __init__(self, theme=default_theme):
267            self._code = theme['code']
268            self._errmsg = theme['errmsg']
269            self._filename = theme['filename']
270            self._indicate = theme['indicate']
271            self._highlight = theme['highlight']
272            self._reset = theme['reset']
273            _DummyColorScheme.__init__(self, theme=theme)
274
275        def _markup(self, msg, color=None, style=Style.BRIGHT):
276            features = ''
277            if color:
278                features += color
279            if style:
280                features += style
281            with ColorShell():
282                with reset_terminal() as mu:
283                    mu += features.encode('utf-8')
284                    mu += (msg).encode('utf-8')
285                return mu.decode('utf-8')
286
287        def code(self, msg):
288            return self._markup(msg, self._code)
289
290        def errmsg(self, msg):
291            return self._markup(msg, self._errmsg)
292
293        def filename(self, msg):
294            return self._markup(msg, self._filename)
295
296        def indicate(self, msg):
297            return self._markup(msg, self._indicate)
298
299        def highlight(self, msg):
300            return self._markup(msg, self._highlight)
301
302        def reset(self, msg):
303            return self._markup(msg, self._reset)
304
305    def termcolor():
306        global _termcolor_inst
307        if _termcolor_inst is None:
308            scheme = themes[numba.core.config.COLOR_SCHEME]
309            _termcolor_inst = HighlightColorScheme(scheme)
310        return _termcolor_inst
311
312feedback_details = """
313Please report the error message and traceback, along with a minimal reproducer
314at: https://github.com/numba/numba/issues/new
315
316If more help is needed please feel free to speak to the Numba core developers
317directly at: https://gitter.im/numba/numba
318
319Thanks in advance for your help in improving Numba!
320"""
321
322unsupported_error_info = """
323Unsupported functionality was found in the code Numba was trying to compile.
324
325If this functionality is important to you please file a feature request at:
326https://github.com/numba/numba/issues/new
327"""
328
329interpreter_error_info = """
330Unsupported Python functionality was found in the code Numba was trying to
331compile. This error could be due to invalid code, does the code work
332without Numba? (To temporarily disable Numba JIT, set the `NUMBA_DISABLE_JIT`
333environment variable to non-zero, and then rerun the code).
334
335If the code is valid and the unsupported functionality is important to you
336please file a feature request at: https://github.com/numba/numba/issues/new
337
338To see Python/NumPy features supported by the latest release of Numba visit:
339https://numba.pydata.org/numba-doc/latest/reference/pysupported.html
340and
341https://numba.pydata.org/numba-doc/latest/reference/numpysupported.html
342"""
343
344constant_inference_info = """
345Numba could not make a constant out of something that it decided should be
346a constant. This could well be a current limitation in Numba's internals,
347however please first check that your code is valid for compilation,
348particularly with respect to string interpolation (not supported!) and
349the requirement of compile time constants as arguments to exceptions:
350https://numba.pydata.org/numba-doc/latest/reference/pysupported.html?highlight=exceptions#constructs
351
352If the code is valid and the unsupported functionality is important to you
353please file a feature request at: https://github.com/numba/numba/issues/new
354
355If you think your code should work with Numba. %s
356""" % feedback_details
357
358typing_error_info = """
359This is not usually a problem with Numba itself but instead often caused by
360the use of unsupported features or an issue in resolving types.
361
362To see Python/NumPy features supported by the latest release of Numba visit:
363https://numba.pydata.org/numba-doc/latest/reference/pysupported.html
364and
365https://numba.pydata.org/numba-doc/latest/reference/numpysupported.html
366
367For more information about typing errors and how to debug them visit:
368https://numba.pydata.org/numba-doc/latest/user/troubleshoot.html#my-code-doesn-t-compile
369
370If you think your code should work with Numba, please report the error message
371and traceback, along with a minimal reproducer at:
372https://github.com/numba/numba/issues/new
373"""
374
375reportable_issue_info = """
376-------------------------------------------------------------------------------
377This should not have happened, a problem has occurred in Numba's internals.
378You are currently using Numba version %s.
379%s
380""" % (numba.__version__, feedback_details)
381
382error_extras = dict()
383error_extras['unsupported_error'] = unsupported_error_info
384error_extras['typing'] = typing_error_info
385error_extras['reportable'] = reportable_issue_info
386error_extras['interpreter'] = interpreter_error_info
387error_extras['constant_inference'] = constant_inference_info
388
389
390def deprecated(arg):
391    """Define a deprecation decorator.
392    An optional string should refer to the new API to be used instead.
393
394    Example:
395      @deprecated
396      def old_func(): ...
397
398      @deprecated('new_func')
399      def old_func(): ..."""
400
401    subst = arg if isinstance(arg, str) else None
402
403    def decorator(func):
404        def wrapper(*args, **kwargs):
405            msg = "Call to deprecated function \"{}\"."
406            if subst:
407                msg += "\n Use \"{}\" instead."
408            warnings.warn(msg.format(func.__name__, subst),
409                          category=DeprecationWarning, stacklevel=2)
410            return func(*args, **kwargs)
411
412        return wraps(func)(wrapper)
413
414    if not subst:
415        return decorator(arg)
416    else:
417        return decorator
418
419
420class WarningsFixer(object):
421    """
422    An object "fixing" warnings of a given category caught during
423    certain phases.  The warnings can have their filename and lineno fixed,
424    and they are deduplicated as well.
425    """
426
427    def __init__(self, category):
428        self._category = category
429        # {(filename, lineno, category) -> messages}
430        self._warnings = defaultdict(set)
431
432    @contextlib.contextmanager
433    def catch_warnings(self, filename=None, lineno=None):
434        """
435        Store warnings and optionally fix their filename and lineno.
436        """
437        with warnings.catch_warnings(record=True) as wlist:
438            warnings.simplefilter('always', self._category)
439            yield
440
441        for w in wlist:
442            msg = str(w.message)
443            if issubclass(w.category, self._category):
444                # Store warnings of this category for deduplication
445                filename = filename or w.filename
446                lineno = lineno or w.lineno
447                self._warnings[filename, lineno, w.category].add(msg)
448            else:
449                # Simply emit other warnings again
450                warnings.warn_explicit(msg, w.category,
451                                       w.filename, w.lineno)
452
453    def flush(self):
454        """
455        Emit all stored warnings.
456        """
457        def key(arg):
458            # It is possible through codegen to create entirely identical
459            # warnings, this leads to comparing types when sorting which breaks
460            # on Python 3. Key as str() and if the worse happens then `id`
461            # creates some uniqueness
462            return str(arg) + str(id(arg))
463
464        for (filename, lineno, category), messages in sorted(
465                self._warnings.items(), key=key):
466            for msg in sorted(messages):
467                warnings.warn_explicit(msg, category, filename, lineno)
468        self._warnings.clear()
469
470
471class NumbaError(Exception):
472
473    def __init__(self, msg, loc=None, highlighting=True):
474        self.msg = msg
475        self.loc = loc
476        if highlighting:
477            highlight = termcolor().errmsg
478        else:
479            def highlight(x):
480                return x
481
482        if loc:
483            new_msg = "%s\n%s\n" % (msg, loc.strformat())
484        else:
485            new_msg = "%s" % (msg,)
486        super(NumbaError, self).__init__(highlight(new_msg))
487
488    @property
489    def contexts(self):
490        try:
491            return self._contexts
492        except AttributeError:
493            self._contexts = lst = []
494            return lst
495
496    def add_context(self, msg):
497        """
498        Add contextual info.  The exception message is expanded with the new
499        contextual information.
500        """
501        self.contexts.append(msg)
502        f = termcolor().errmsg('{0}\n') + termcolor().filename('During: {1}')
503        newmsg = f.format(self, msg)
504        self.args = (newmsg,)
505        return self
506
507    def patch_message(self, new_message):
508        """
509        Change the error message to the given new message.
510        """
511        self.args = (new_message,) + self.args[1:]
512
513
514class UnsupportedError(NumbaError):
515    """
516    Numba does not have an implementation for this functionality.
517    """
518    pass
519
520
521class UnsupportedRewriteError(UnsupportedError):
522    """UnsupportedError from rewrite passes
523    """
524    pass
525
526
527class IRError(NumbaError):
528    """
529    An error occurred during Numba IR generation.
530    """
531    pass
532
533
534class RedefinedError(IRError):
535    """
536    An error occurred during interpretation of IR due to variable redefinition.
537    """
538    pass
539
540
541class NotDefinedError(IRError):
542    """
543    An undefined variable is encountered during interpretation of IR.
544    """
545
546    def __init__(self, name, loc=None):
547        self.name = name
548        msg = "Variable '%s' is not defined." % name
549        super(NotDefinedError, self).__init__(msg, loc=loc)
550
551
552class VerificationError(IRError):
553    """
554    An error occurred during IR verification. Once Numba's internal
555    representation (IR) is constructed it is then verified to ensure that
556    terminators are both present and in the correct places within the IR. If
557    it is the case that this condition is not met, a VerificationError is
558    raised.
559    """
560    pass
561
562
563class MacroError(NumbaError):
564    """
565    An error occurred during macro expansion.
566    """
567    pass
568
569
570class DeprecationError(NumbaError):
571    """
572    Functionality is deprecated.
573    """
574    pass
575
576
577class LoweringError(NumbaError):
578    """
579    An error occurred during lowering.
580    """
581
582    def __init__(self, msg, loc=None):
583        super(LoweringError, self).__init__(msg, loc=loc)
584
585
586class UnsupportedParforsError(NumbaError):
587    """
588    An error ocurred because parfors is not supported on the platform.
589    """
590    pass
591
592
593class ForbiddenConstruct(LoweringError):
594    """
595    A forbidden Python construct was encountered (e.g. use of locals()).
596    """
597    pass
598
599
600class TypingError(NumbaError):
601    """
602    A type inference failure.
603    """
604    pass
605
606
607class UntypedAttributeError(TypingError):
608    def __init__(self, value, attr, loc=None):
609        module = getattr(value, 'pymod', None)
610        if module is not None and module == np:
611            # unsupported numpy feature.
612            msg = ("Use of unsupported NumPy function 'numpy.%s' "
613                   "or unsupported use of the function.") % attr
614        else:
615            msg = "Unknown attribute '{attr}' of type {type}"
616            msg = msg.format(type=value, attr=attr)
617        super(UntypedAttributeError, self).__init__(msg, loc=loc)
618
619
620class ByteCodeSupportError(NumbaError):
621    """
622    Failure to extract the bytecode of the user's function.
623    """
624
625    def __init__(self, msg, loc=None):
626        super(ByteCodeSupportError, self).__init__(msg, loc=loc)
627
628
629class CompilerError(NumbaError):
630    """
631    Some high-level error in the compiler.
632    """
633    pass
634
635
636class ConstantInferenceError(NumbaError):
637    """
638    Failure during constant inference.
639    """
640
641    def __init__(self, value, loc=None):
642        super(ConstantInferenceError, self).__init__(value, loc=loc)
643
644
645class InternalError(NumbaError):
646    """
647    For wrapping internal error occured within the compiler
648    """
649
650    def __init__(self, exception):
651        super(InternalError, self).__init__(str(exception))
652        self.old_exception = exception
653
654
655class RequireLiteralValue(TypingError):
656    """
657    For signalling that a function's typing requires a constant value for
658    some of its arguments.
659    """
660    pass
661
662
663class ForceLiteralArg(NumbaError):
664    """A Pseudo-exception to signal the dispatcher to type an argument literally
665
666    Attributes
667    ----------
668    requested_args : frozenset[int]
669        requested positions of the arguments.
670    """
671    def __init__(self, arg_indices, fold_arguments=None, loc=None):
672        """
673        Parameters
674        ----------
675        arg_indices : Sequence[int]
676            requested positions of the arguments.
677        fold_arguments: callable
678            A function ``(tuple, dict) -> tuple`` that binds and flattens
679            the ``args`` and ``kwargs``.
680        loc : numba.ir.Loc or None
681        """
682        super(ForceLiteralArg, self).__init__(
683            "Pseudo-exception to force literal arguments in the dispatcher",
684            loc=loc,
685        )
686        self.requested_args = frozenset(arg_indices)
687        self.fold_arguments = fold_arguments
688
689    def bind_fold_arguments(self, fold_arguments):
690        """Bind the fold_arguments function
691        """
692        e = ForceLiteralArg(self.requested_args, fold_arguments,
693                            loc=self.loc)
694        return chain_exception(e, self)
695
696    def combine(self, other):
697        """Returns a new instance by or'ing the requested_args.
698        """
699        if not isinstance(other, ForceLiteralArg):
700            m = '*other* must be a {} but got a {} instead'
701            raise TypeError(m.format(ForceLiteralArg, type(other)))
702        return ForceLiteralArg(self.requested_args | other.requested_args)
703
704    def __or__(self, other):
705        """Same as self.combine(other)
706        """
707        return self.combine(other)
708
709
710class LiteralTypingError(TypingError):
711    """
712    Failure in typing a Literal type
713    """
714    pass
715
716
717def _format_msg(fmt, args, kwargs):
718    return fmt.format(*args, **kwargs)
719
720
721_numba_path = os.path.dirname(__file__)
722loc_info = {}
723
724
725@contextlib.contextmanager
726def new_error_context(fmt_, *args, **kwargs):
727    """
728    A contextmanager that prepend contextual information to any exception
729    raised within.  If the exception type is not an instance of NumbaError,
730    it will be wrapped into a InternalError.   The exception class can be
731    changed by providing a "errcls_" keyword argument with the exception
732    constructor.
733
734    The first argument is a message that describes the context.  It can be a
735    format string.  If there are additional arguments, it will be used as
736    ``fmt_.format(*args, **kwargs)`` to produce the final message string.
737    """
738    errcls = kwargs.pop('errcls_', InternalError)
739
740    loc = kwargs.get('loc', None)
741    if loc is not None and not loc.filename.startswith(_numba_path):
742        loc_info.update(kwargs)
743
744    try:
745        yield
746    except NumbaError as e:
747        e.add_context(_format_msg(fmt_, args, kwargs))
748        raise
749    except Exception as e:
750        newerr = errcls(e).add_context(_format_msg(fmt_, args, kwargs))
751        tb = sys.exc_info()[2] if numba.core.config.FULL_TRACEBACKS else None
752        reraise(type(newerr), newerr, tb)
753
754
755__all__ += [name for (name, value) in globals().items()
756            if not name.startswith('_') and isinstance(value, type)
757            and issubclass(value, (Exception, Warning))]
758