Commit 78d0c76f authored by Kirill Smelkov's avatar Kirill Smelkov

golang: Teach pyerror to be a base class

It is surprising to have an exception class that cannot be derived from.

Besides, in the future we'll use subclassing from golang.error as an
indicator that an error is a "well-defined" (in simple words - does not
need traceback to be interpreted).
parent 17798442
......@@ -241,8 +241,14 @@ cdef class pychan:
#
# There can be multiple pyerror(s) wrapping a particular raw error object.
# Nil C-level error corresponds to None at Python-level.
#
# Pyerror can be also used as base class for Python-level exception types:
#
# - objects with type being exact pyerror are treated as wrappers around C-level error.
# - objects with other types inherited from pyerror are treated as Python-level error.
cdef class pyerror(Exception):
cdef error err # raw error object
cdef error err # raw error object; nil for Python-level case
cdef readonly args # .args for Python-level case
# pyerror.from_error returns pyerror wrapping pyx/nogil-level error.
# from_error(nil) -> returns None.
......
......@@ -867,13 +867,21 @@ cdef class pyerror(Exception):
pyerr.err = errors_New_pyexc(pyb(arg))
return
raise TypeError("subclassing error is not supported yet")
# class MyError(error); MyError(...)
# just store .args and be done with it (we already did ^^^)
pass
def __dealloc__(pyerror pyerr):
pyerr.err = nil
def Error(pyerror pyerr):
"""Error returns string that represents the error."""
# python-level case
if type(pyerr) is not pyerror:
# subclass should override Error, but provide at least something by default
return repr(pyerr)
# wrapper around C-level error
assert pyerr.err != nil
return pyerr.err.Error()
......@@ -884,6 +892,11 @@ cdef class pyerror(Exception):
# pyerror == pyerror
def __hash__(pyerror pyerr):
# python-level case
if type(pyerr) is not pyerror:
return hash(type(pyerr)) ^ hash(pyerr.args)
# wrapper around C-level error
# TODO use std::hash directly
cdef const type_info* typ = &typeid(pyerr.err._ptr()[0])
return hash(typ.name()) ^ hash(pyerr.err.Error())
......@@ -893,6 +906,11 @@ cdef class pyerror(Exception):
if type(a) is not type(rhs):
return False
# python-level case
if type(a) is not pyerror:
return a.args == rhs.args
# wrapper around C-level error
cdef pyerror b = rhs
cdef const type_info* atype = &typeid(a.err._ptr()[0])
cdef const type_info* btype = &typeid(b.err._ptr()[0])
......@@ -908,6 +926,11 @@ cdef class pyerror(Exception):
def __repr__(pyerror pyerr):
typ = type(pyerr)
# python-level case
if typ is not pyerror:
return "%s.%s%r" % (typ.__module__, typ.__name__, pyerr.args)
# wrapper around C-level error
cdef const type_info* ctype = &typeid(pyerr.err._ptr()[0])
# TODO demangle type name (e.g. abi::__cxa_demangle)
return "<%s.%s object ctype=%s error=%s>" % (typ.__module__, typ.__name__, ctype.name(), pyqq(pyerr.Error()))
......
......@@ -50,6 +50,18 @@ def assertEne(e1, e2):
#assert hash(e1) != hash(e2)
# EError is custom error class that inherits from error.
class EError(error):
def __init__(myerr, op, ret):
myerr.op = op
myerr.ret = ret
def Error(myerr): return "my %s: %s" % (myerr.op, myerr.ret)
# no .Unwrap()
# NOTE error provides good __eq__ and __hash__ out of the box.
# test for golang.error class.
def test_error():
assert error_mkchain([]) is None
......@@ -89,6 +101,22 @@ def test_error():
assert e.Unwrap() is None
assertEeq(e, e)
# create an error from py via error subclass
class EErr(error): pass
m = EErr("abc")
assertEeq(m, m)
assertEne(m, error("abc")) # EErr("abc") != error("abc")
epy = EError("load", 3)
assert type(epy) is EError
assert epy.Error() == "my load: 3"
assert str(epy) == "my load: 3"
assert repr(epy) == "golang.errors_test.EError('load', 3)"
assert epy.Unwrap() is None
assertEeq(epy, epy)
assertEeq(epy, EError("load", 3))
assertEne(epy, EError("load", 4))
def test_new():
E = errors.New
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment