1#
2#   Errors
3#
4
5from __future__ import absolute_import
6
7try:
8    from __builtin__ import basestring as any_string_type
9except ImportError:
10    any_string_type = (bytes, str)
11
12import sys
13from contextlib import contextmanager
14
15from ..Utils import open_new_file
16from . import DebugFlags
17from . import Options
18
19
20class PyrexError(Exception):
21    pass
22
23
24class PyrexWarning(Exception):
25    pass
26
27class CannotSpecialize(PyrexError):
28    pass
29
30def context(position):
31    source = position[0]
32    assert not (isinstance(source, any_string_type)), (
33        "Please replace filename strings with Scanning.FileSourceDescriptor instances %r" % source)
34    try:
35        F = source.get_lines()
36    except UnicodeDecodeError:
37        # file has an encoding problem
38        s = u"[unprintable code]\n"
39    else:
40        s = u''.join(F[max(0, position[1]-6):position[1]])
41        s = u'...\n%s%s^\n' % (s, u' '*(position[2]-1))
42    s = u'%s\n%s%s\n' % (u'-'*60, s, u'-'*60)
43    return s
44
45def format_position(position):
46    if position:
47        return u"%s:%d:%d: " % (position[0].get_error_description(),
48                                position[1], position[2])
49    return u''
50
51def format_error(message, position):
52    if position:
53        pos_str = format_position(position)
54        cont = context(position)
55        message = u'\nError compiling Cython file:\n%s\n%s%s' % (cont, pos_str, message or u'')
56    return message
57
58class CompileError(PyrexError):
59
60    def __init__(self, position = None, message = u""):
61        self.position = position
62        self.message_only = message
63        self.formatted_message = format_error(message, position)
64        self.reported = False
65        Exception.__init__(self, self.formatted_message)
66        # Python Exception subclass pickling is broken,
67        # see https://bugs.python.org/issue1692335
68        self.args = (position, message)
69
70    def __str__(self):
71        return self.formatted_message
72
73class CompileWarning(PyrexWarning):
74
75    def __init__(self, position = None, message = ""):
76        self.position = position
77        Exception.__init__(self, format_position(position) + message)
78
79class InternalError(Exception):
80    # If this is ever raised, there is a bug in the compiler.
81
82    def __init__(self, message):
83        self.message_only = message
84        Exception.__init__(self, u"Internal compiler error: %s"
85            % message)
86
87class AbortError(Exception):
88    # Throw this to stop the compilation immediately.
89
90    def __init__(self, message):
91        self.message_only = message
92        Exception.__init__(self, u"Abort error: %s" % message)
93
94class CompilerCrash(CompileError):
95    # raised when an unexpected exception occurs in a transform
96    def __init__(self, pos, context, message, cause, stacktrace=None):
97        if message:
98            message = u'\n' + message
99        else:
100            message = u'\n'
101        self.message_only = message
102        if context:
103            message = u"Compiler crash in %s%s" % (context, message)
104        if stacktrace:
105            import traceback
106            message += (
107                u'\n\nCompiler crash traceback from this point on:\n' +
108                u''.join(traceback.format_tb(stacktrace)))
109        if cause:
110            if not stacktrace:
111                message += u'\n'
112            message += u'%s: %s' % (cause.__class__.__name__, cause)
113        CompileError.__init__(self, pos, message)
114        # Python Exception subclass pickling is broken,
115        # see https://bugs.python.org/issue1692335
116        self.args = (pos, context, message, cause, stacktrace)
117
118class NoElementTreeInstalledException(PyrexError):
119    """raised when the user enabled options.gdb_debug but no ElementTree
120    implementation was found
121    """
122
123listing_file = None
124num_errors = 0
125echo_file = None
126
127def open_listing_file(path, echo_to_stderr = 1):
128    # Begin a new error listing. If path is None, no file
129    # is opened, the error counter is just reset.
130    global listing_file, num_errors, echo_file
131    if path is not None:
132        listing_file = open_new_file(path)
133    else:
134        listing_file = None
135    if echo_to_stderr:
136        echo_file = sys.stderr
137    else:
138        echo_file = None
139    num_errors = 0
140
141def close_listing_file():
142    global listing_file
143    if listing_file:
144        listing_file.close()
145        listing_file = None
146
147def report_error(err, use_stack=True):
148    if error_stack and use_stack:
149        error_stack[-1].append(err)
150    else:
151        global num_errors
152        # See Main.py for why dual reporting occurs. Quick fix for now.
153        if err.reported: return
154        err.reported = True
155        try: line = u"%s\n" % err
156        except UnicodeEncodeError:
157            # Python <= 2.5 does this for non-ASCII Unicode exceptions
158            line = format_error(getattr(err, 'message_only', "[unprintable exception message]"),
159                                getattr(err, 'position', None)) + u'\n'
160        if listing_file:
161            try: listing_file.write(line)
162            except UnicodeEncodeError:
163                listing_file.write(line.encode('ASCII', 'replace'))
164        if echo_file:
165            try: echo_file.write(line)
166            except UnicodeEncodeError:
167                echo_file.write(line.encode('ASCII', 'replace'))
168        num_errors += 1
169        if Options.fast_fail:
170            raise AbortError("fatal errors")
171
172def error(position, message):
173    #print("Errors.error:", repr(position), repr(message)) ###
174    if position is None:
175        raise InternalError(message)
176    err = CompileError(position, message)
177    if DebugFlags.debug_exception_on_error: raise Exception(err)  # debug
178    report_error(err)
179    return err
180
181
182LEVEL = 1  # warn about all errors level 1 or higher
183
184def _write_file_encode(file, line):
185    try:
186        file.write(line)
187    except UnicodeEncodeError:
188        file.write(line.encode('ascii', 'replace'))
189
190
191def message(position, message, level=1):
192    if level < LEVEL:
193        return
194    warn = CompileWarning(position, message)
195    line = u"note: %s\n" % warn
196    if listing_file:
197        _write_file_encode(listing_file, line)
198    if echo_file:
199        _write_file_encode(echo_file, line)
200    return warn
201
202
203def warning(position, message, level=0):
204    if level < LEVEL:
205        return
206    if Options.warning_errors and position:
207        return error(position, message)
208    warn = CompileWarning(position, message)
209    line = u"warning: %s\n" % warn
210    if listing_file:
211        _write_file_encode(listing_file, line)
212    if echo_file:
213        _write_file_encode(echo_file, line)
214    return warn
215
216
217_warn_once_seen = {}
218def warn_once(position, message, level=0):
219    if level < LEVEL or message in _warn_once_seen:
220        return
221    warn = CompileWarning(position, message)
222    line = u"warning: %s\n" % warn
223    if listing_file:
224        _write_file_encode(listing_file, line)
225    if echo_file:
226        _write_file_encode(echo_file, line)
227    _warn_once_seen[message] = True
228    return warn
229
230
231# These functions can be used to momentarily suppress errors.
232
233error_stack = []
234
235
236def hold_errors():
237    error_stack.append([])
238
239
240def release_errors(ignore=False):
241    held_errors = error_stack.pop()
242    if not ignore:
243        for err in held_errors:
244            report_error(err)
245
246
247def held_errors():
248    return error_stack[-1]
249
250
251# same as context manager:
252
253@contextmanager
254def local_errors(ignore=False):
255    errors = []
256    error_stack.append(errors)
257    try:
258        yield errors
259    finally:
260        release_errors(ignore=ignore)
261
262
263# this module needs a redesign to support parallel cythonisation, but
264# for now, the following works at least in sequential compiler runs
265
266def reset():
267    _warn_once_seen.clear()
268    del error_stack[:]
269