diff --git a/python_exceptions.go b/python_exceptions.go index 9327f8f..ab6adc4 100644 --- a/python_exceptions.go +++ b/python_exceptions.go @@ -52,7 +52,34 @@ func raisePythonException(err error) { backtrace := C.CString(evalErr.Backtrace()) defer C.free(unsafe.Pointer(backtrace)) - exc_args = C.makeEvalErrorArgs(error_msg, error_type, backtrace) + var ( + function_name *C.char + filename *C.char + line C.uint + column C.uint + ) + + if len(evalErr.CallStack) > 0 { + frame := evalErr.CallStack[len(evalErr.CallStack)-1] + + filename = C.CString(frame.Pos.Filename()) + defer C.free(unsafe.Pointer((filename))) + + line = C.uint(frame.Pos.Line) + column = C.uint(frame.Pos.Col) + + function_name = C.CString(frame.Name) + defer C.free(unsafe.Pointer(function_name)) + } else { + filename = C.CString("unknown") + defer C.free(unsafe.Pointer(filename)) + + line = 0 + column = 0 + function_name = filename + } + + exc_args = C.makeEvalErrorArgs(error_msg, error_type, filename, line, column, function_name, backtrace) exc_type = C.EvalError case errors.As(err, &resolveErr): items := C.PyTuple_New(C.Py_ssize_t(len(resolveErr))) diff --git a/src/starlark_go/errors.py b/src/starlark_go/errors.py index ed638a0..d311d46 100644 --- a/src/starlark_go/errors.py +++ b/src/starlark_go/errors.py @@ -84,8 +84,43 @@ class EvalError(StarlarkError): such as adding a string to an integer. """ - def __init__(self, error: str, error_type: str, backtrace: str): - super().__init__(error, error_type, backtrace) + def __init__( + self, + error: str, + error_type: str, + filename: str, + line: int, + column: int, + function_name: str, + backtrace: str, + ): + super().__init__( + error, error_type, filename, line, column, function_name, backtrace + ) + self.filename = filename + """ + The name of the file that the error occurred in. + + :type: str + """ + self.line = line + """ + The line number that the error occurred on (1-based) + + :type: int + """ + self.column = column + """ + The column that the error occurred on (1-based) + + :type: int + """ + self.function_name = function_name + """ + The name of the function that the error occurred in + + :type: str + """ self.backtrace = backtrace """ A backtrace through Starlark's stack leading up to the error. @@ -93,6 +128,12 @@ def __init__(self, error: str, error_type: str, backtrace: str): :type: str """ + context = self.filename + if self.function_name != "": + context += " in " + self.function_name + + self.error = f"{context}:{self.line}:{self.column}: {self.error}" + class ResolveErrorItem: """ diff --git a/starlark.c b/starlark.c index d874a6d..efbebe6 100644 --- a/starlark.c +++ b/starlark.c @@ -422,12 +422,20 @@ PyObject *makeSyntaxErrorArgs( } PyObject *makeEvalErrorArgs( - const char *error_msg, const char *error_type, const char *backtrace + const char *error_msg, + const char *error_type, + const char *filename, + const unsigned int line, + const unsigned int column, + const char *function_name, + const char *backtrace ) { /* Necessary because Cgo can't do varargs */ /* Three strings */ - return Py_BuildValue("sss", error_msg, error_type, backtrace); + return Py_BuildValue( + "sssIIss", error_msg, error_type, filename, line, column, function_name, backtrace + ); } PyObject *makeResolveErrorItem( diff --git a/starlark.h b/starlark.h index d93674a..36b2131 100644 --- a/starlark.h +++ b/starlark.h @@ -54,7 +54,13 @@ PyObject *makeSyntaxErrorArgs( ); PyObject *makeEvalErrorArgs( - const char *error_msg, const char *error_type, const char *backtrace + const char *error_msg, + const char *error_type, + const char *filename, + const unsigned int line, + const unsigned int column, + const char *function_name, + const char *backtrace ); PyObject *makeResolveErrorItem( diff --git a/tests/test_evalerror.py b/tests/test_evalerror.py index a125a6f..8efee3b 100644 --- a/tests/test_evalerror.py +++ b/tests/test_evalerror.py @@ -2,6 +2,11 @@ from starlark_go import EvalError, Starlark +STARLARK_SRC = """ +def wrong(): + return 1 + "2" +""" + def test_raises_evalerror(): s = Starlark() @@ -17,16 +22,35 @@ def test_eval_attrs(): s = Starlark() raised = False + s.exec(STARLARK_SRC, filename="fake.star") + try: - s.eval('1 + "2"') + s.eval("wrong()") except EvalError as e: assert hasattr(e, "error") assert isinstance(e.error, str) assert hasattr(e, "error_type") assert isinstance(e.error_type, str) assert e.error_type == "*starlark.EvalError" + assert hasattr(e, "filename") + assert isinstance(e.filename, str) + assert e.filename == "fake.star" + assert hasattr(e, "line") + assert isinstance(e.line, int) + assert e.line == 3 + assert hasattr(e, "column") + assert isinstance(e.column, int) + assert e.column == 12 + assert hasattr(e, "function_name") + assert isinstance(e.function_name, str) + assert e.function_name == "wrong" assert hasattr(e, "backtrace") assert isinstance(e.backtrace, str) + + strerror = str(e) + assert strerror.startswith( + f"{e.filename} in {e.function_name}:{e.line}:{e.column}: " + ) raised = True assert raised