301 lines
9.1 KiB
Python
301 lines
9.1 KiB
Python
#
|
|
# Errors
|
|
#
|
|
|
|
from __future__ import absolute_import
|
|
|
|
try:
|
|
from __builtin__ import basestring as any_string_type
|
|
except ImportError:
|
|
any_string_type = (bytes, str)
|
|
|
|
import sys
|
|
from contextlib import contextmanager
|
|
|
|
try:
|
|
from threading import local as _threadlocal
|
|
except ImportError:
|
|
class _threadlocal(object): pass
|
|
|
|
threadlocal = _threadlocal()
|
|
|
|
from ..Utils import open_new_file
|
|
from . import DebugFlags
|
|
from . import Options
|
|
|
|
|
|
class PyrexError(Exception):
|
|
pass
|
|
|
|
|
|
class PyrexWarning(Exception):
|
|
pass
|
|
|
|
class CannotSpecialize(PyrexError):
|
|
pass
|
|
|
|
def context(position):
|
|
source = position[0]
|
|
assert not (isinstance(source, any_string_type)), (
|
|
"Please replace filename strings with Scanning.FileSourceDescriptor instances %r" % source)
|
|
try:
|
|
F = source.get_lines()
|
|
except UnicodeDecodeError:
|
|
# file has an encoding problem
|
|
s = u"[unprintable code]\n"
|
|
else:
|
|
s = u''.join(F[max(0, position[1]-6):position[1]])
|
|
s = u'...\n%s%s^\n' % (s, u' '*(position[2]))
|
|
s = u'%s\n%s%s\n' % (u'-'*60, s, u'-'*60)
|
|
return s
|
|
|
|
def format_position(position):
|
|
if position:
|
|
return u"%s:%d:%d: " % (position[0].get_error_description(),
|
|
position[1], position[2])
|
|
return u''
|
|
|
|
def format_error(message, position):
|
|
if position:
|
|
pos_str = format_position(position)
|
|
cont = context(position)
|
|
message = u'\nError compiling Cython file:\n%s\n%s%s' % (cont, pos_str, message or u'')
|
|
return message
|
|
|
|
class CompileError(PyrexError):
|
|
|
|
def __init__(self, position = None, message = u""):
|
|
self.position = position
|
|
self.message_only = message
|
|
self.formatted_message = format_error(message, position)
|
|
self.reported = False
|
|
Exception.__init__(self, self.formatted_message)
|
|
# Python Exception subclass pickling is broken,
|
|
# see https://bugs.python.org/issue1692335
|
|
self.args = (position, message)
|
|
|
|
def __str__(self):
|
|
return self.formatted_message
|
|
|
|
class CompileWarning(PyrexWarning):
|
|
|
|
def __init__(self, position = None, message = ""):
|
|
self.position = position
|
|
Exception.__init__(self, format_position(position) + message)
|
|
|
|
class InternalError(Exception):
|
|
# If this is ever raised, there is a bug in the compiler.
|
|
|
|
def __init__(self, message):
|
|
self.message_only = message
|
|
Exception.__init__(self, u"Internal compiler error: %s"
|
|
% message)
|
|
|
|
class AbortError(Exception):
|
|
# Throw this to stop the compilation immediately.
|
|
|
|
def __init__(self, message):
|
|
self.message_only = message
|
|
Exception.__init__(self, u"Abort error: %s" % message)
|
|
|
|
class CompilerCrash(CompileError):
|
|
# raised when an unexpected exception occurs in a transform
|
|
def __init__(self, pos, context, message, cause, stacktrace=None):
|
|
if message:
|
|
message = u'\n' + message
|
|
else:
|
|
message = u'\n'
|
|
self.message_only = message
|
|
if context:
|
|
message = u"Compiler crash in %s%s" % (context, message)
|
|
if stacktrace:
|
|
import traceback
|
|
message += (
|
|
u'\n\nCompiler crash traceback from this point on:\n' +
|
|
u''.join(traceback.format_tb(stacktrace)))
|
|
if cause:
|
|
if not stacktrace:
|
|
message += u'\n'
|
|
message += u'%s: %s' % (cause.__class__.__name__, cause)
|
|
CompileError.__init__(self, pos, message)
|
|
# Python Exception subclass pickling is broken,
|
|
# see https://bugs.python.org/issue1692335
|
|
self.args = (pos, context, message, cause, stacktrace)
|
|
|
|
class NoElementTreeInstalledException(PyrexError):
|
|
"""raised when the user enabled options.gdb_debug but no ElementTree
|
|
implementation was found
|
|
"""
|
|
|
|
def open_listing_file(path, echo_to_stderr=True):
|
|
# Begin a new error listing. If path is None, no file
|
|
# is opened, the error counter is just reset.
|
|
if path is not None:
|
|
threadlocal.cython_errors_listing_file = open_new_file(path)
|
|
else:
|
|
threadlocal.cython_errors_listing_file = None
|
|
if echo_to_stderr:
|
|
threadlocal.cython_errors_echo_file = sys.stderr
|
|
else:
|
|
threadlocal.cython_errors_echo_file = None
|
|
threadlocal.cython_errors_count = 0
|
|
|
|
def close_listing_file():
|
|
if threadlocal.cython_errors_listing_file:
|
|
threadlocal.cython_errors_listing_file.close()
|
|
threadlocal.cython_errors_listing_file = None
|
|
|
|
def report_error(err, use_stack=True):
|
|
error_stack = threadlocal.cython_errors_stack
|
|
if error_stack and use_stack:
|
|
error_stack[-1].append(err)
|
|
else:
|
|
# See Main.py for why dual reporting occurs. Quick fix for now.
|
|
if err.reported: return
|
|
err.reported = True
|
|
try: line = u"%s\n" % err
|
|
except UnicodeEncodeError:
|
|
# Python <= 2.5 does this for non-ASCII Unicode exceptions
|
|
line = format_error(getattr(err, 'message_only', "[unprintable exception message]"),
|
|
getattr(err, 'position', None)) + u'\n'
|
|
listing_file = threadlocal.cython_errors_listing_file
|
|
if listing_file:
|
|
try: listing_file.write(line)
|
|
except UnicodeEncodeError:
|
|
listing_file.write(line.encode('ASCII', 'replace'))
|
|
echo_file = threadlocal.cython_errors_echo_file
|
|
if echo_file:
|
|
try: echo_file.write(line)
|
|
except UnicodeEncodeError:
|
|
echo_file.write(line.encode('ASCII', 'replace'))
|
|
threadlocal.cython_errors_count += 1
|
|
if Options.fast_fail:
|
|
raise AbortError("fatal errors")
|
|
|
|
def error(position, message):
|
|
#print("Errors.error:", repr(position), repr(message)) ###
|
|
if position is None:
|
|
raise InternalError(message)
|
|
err = CompileError(position, message)
|
|
if DebugFlags.debug_exception_on_error: raise Exception(err) # debug
|
|
report_error(err)
|
|
return err
|
|
|
|
|
|
LEVEL = 1 # warn about all errors level 1 or higher
|
|
|
|
def _write_file_encode(file, line):
|
|
try:
|
|
file.write(line)
|
|
except UnicodeEncodeError:
|
|
file.write(line.encode('ascii', 'replace'))
|
|
|
|
|
|
def performance_hint(position, message, env):
|
|
if not env.directives['show_performance_hints']:
|
|
return
|
|
warn = CompileWarning(position, message)
|
|
line = "performance hint: %s\n" % warn
|
|
listing_file = threadlocal.cython_errors_listing_file
|
|
if listing_file:
|
|
_write_file_encode(listing_file, line)
|
|
echo_file = threadlocal.cython_errors_echo_file
|
|
if echo_file:
|
|
_write_file_encode(echo_file, line)
|
|
return warn
|
|
|
|
|
|
def message(position, message, level=1):
|
|
if level < LEVEL:
|
|
return
|
|
warn = CompileWarning(position, message)
|
|
line = u"note: %s\n" % warn
|
|
listing_file = threadlocal.cython_errors_listing_file
|
|
if listing_file:
|
|
_write_file_encode(listing_file, line)
|
|
echo_file = threadlocal.cython_errors_echo_file
|
|
if echo_file:
|
|
_write_file_encode(echo_file, line)
|
|
return warn
|
|
|
|
|
|
def warning(position, message, level=0):
|
|
if level < LEVEL:
|
|
return
|
|
if Options.warning_errors and position:
|
|
return error(position, message)
|
|
warn = CompileWarning(position, message)
|
|
line = u"warning: %s\n" % warn
|
|
listing_file = threadlocal.cython_errors_listing_file
|
|
if listing_file:
|
|
_write_file_encode(listing_file, line)
|
|
echo_file = threadlocal.cython_errors_echo_file
|
|
if echo_file:
|
|
_write_file_encode(echo_file, line)
|
|
return warn
|
|
|
|
|
|
def warn_once(position, message, level=0):
|
|
if level < LEVEL:
|
|
return
|
|
warn_once_seen = threadlocal.cython_errors_warn_once_seen
|
|
if message in warn_once_seen:
|
|
return
|
|
warn = CompileWarning(position, message)
|
|
line = u"warning: %s\n" % warn
|
|
listing_file = threadlocal.cython_errors_listing_file
|
|
if listing_file:
|
|
_write_file_encode(listing_file, line)
|
|
echo_file = threadlocal.cython_errors_echo_file
|
|
if echo_file:
|
|
_write_file_encode(echo_file, line)
|
|
warn_once_seen.add(message)
|
|
return warn
|
|
|
|
|
|
# These functions can be used to momentarily suppress errors.
|
|
|
|
def hold_errors():
|
|
errors = []
|
|
threadlocal.cython_errors_stack.append(errors)
|
|
return errors
|
|
|
|
|
|
def release_errors(ignore=False):
|
|
held_errors = threadlocal.cython_errors_stack.pop()
|
|
if not ignore:
|
|
for err in held_errors:
|
|
report_error(err)
|
|
|
|
|
|
def held_errors():
|
|
return threadlocal.cython_errors_stack[-1]
|
|
|
|
|
|
# same as context manager:
|
|
|
|
@contextmanager
|
|
def local_errors(ignore=False):
|
|
errors = hold_errors()
|
|
try:
|
|
yield errors
|
|
finally:
|
|
release_errors(ignore=ignore)
|
|
|
|
|
|
# Keep all global state in thread local storage to support parallel cythonisation in distutils.
|
|
|
|
def init_thread():
|
|
threadlocal.cython_errors_count = 0
|
|
threadlocal.cython_errors_listing_file = None
|
|
threadlocal.cython_errors_echo_file = None
|
|
threadlocal.cython_errors_warn_once_seen = set()
|
|
threadlocal.cython_errors_stack = []
|
|
|
|
def reset():
|
|
threadlocal.cython_errors_warn_once_seen.clear()
|
|
del threadlocal.cython_errors_stack[:]
|
|
|
|
def get_errors_count():
|
|
return threadlocal.cython_errors_count
|