mirror of
https://github.com/nim-lang/Nim.git
synced 2026-06-04 19:04:46 +00:00
@@ -37,6 +37,10 @@
|
||||
the use of `static[T]` types.
|
||||
(#6415)
|
||||
|
||||
- Native C++ exceptions can now be imported with `importcpp` pragma.
|
||||
Imported exceptions can be raised and caught just like Nim exceptions.
|
||||
More details in language manual.
|
||||
|
||||
### Tool changes
|
||||
|
||||
- ``jsondoc2`` has been renamed ``jsondoc``, similar to how ``doc2`` was renamed
|
||||
|
||||
@@ -1674,6 +1674,19 @@ proc isException*(t: PType): bool =
|
||||
base = base.lastSon
|
||||
return false
|
||||
|
||||
proc isImportedException*(t: PType): bool =
|
||||
assert(t != nil)
|
||||
if optNoCppExceptions in gGlobalOptions:
|
||||
return false
|
||||
|
||||
let base = t.skipTypes({tyAlias, tyPtr, tyDistinct, tyGenericInst})
|
||||
|
||||
if base.sym != nil and sfCompileToCpp in base.sym.flags:
|
||||
result = true
|
||||
|
||||
proc isInfixAs*(n: PNode): bool =
|
||||
return n.kind == nkInfix and n[0].kind == nkIdent and n[0].ident.id == getIdent("as").id
|
||||
|
||||
proc findUnresolvedStatic*(n: PNode): PNode =
|
||||
if n.kind == nkSym and n.typ.kind == tyStatic and n.typ.n == nil:
|
||||
return n
|
||||
|
||||
@@ -577,15 +577,18 @@ proc genRaiseStmt(p: BProc, t: PNode) =
|
||||
# we must execute it before reraising
|
||||
var finallyBlock = p.nestedTryStmts[^1].n[^1]
|
||||
if finallyBlock.kind == nkFinally:
|
||||
genSimpleBlock(p, finallyBlock.sons[0])
|
||||
if t.sons[0].kind != nkEmpty:
|
||||
genSimpleBlock(p, finallyBlock[0])
|
||||
if t[0].kind != nkEmpty:
|
||||
var a: TLoc
|
||||
initLocExpr(p, t.sons[0], a)
|
||||
initLocExprSingleUse(p, t[0], a)
|
||||
var e = rdLoc(a)
|
||||
var typ = skipTypes(t.sons[0].typ, abstractPtrs)
|
||||
var typ = skipTypes(t[0].typ, abstractPtrs)
|
||||
genLineDir(p, t)
|
||||
lineCg(p, cpsStmts, "#raiseException((#Exception*)$1, $2);$n",
|
||||
[e, makeCString(typ.sym.name.s)])
|
||||
if isImportedException(typ):
|
||||
lineF(p, cpsStmts, "throw $1;$n", [e])
|
||||
else:
|
||||
lineCg(p, cpsStmts, "#raiseException((#Exception*)$1, $2);$n",
|
||||
[e, makeCString(typ.sym.name.s)])
|
||||
else:
|
||||
genLineDir(p, t)
|
||||
# reraise the last exception:
|
||||
@@ -799,19 +802,16 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) =
|
||||
# general_handler_body
|
||||
# }
|
||||
# finallyPart();
|
||||
|
||||
|
||||
template genExceptBranchBody(body: PNode) {.dirty.} =
|
||||
if optStackTrace in p.options:
|
||||
linefmt(p, cpsStmts, "#setFrame((TFrame*)&FR_);$n")
|
||||
expr(p, body, d)
|
||||
linefmt(p, cpsStmts, "#popCurrentException();$n")
|
||||
|
||||
if not isEmptyType(t.typ) and d.k == locNone:
|
||||
getTemp(p, t.typ, d)
|
||||
genLineDir(p, t)
|
||||
|
||||
let end_label = getLabel(p)
|
||||
discard cgsym(p.module, "Exception")
|
||||
discard cgsym(p.module, "popCurrentExceptionEx")
|
||||
add(p.nestedTryStmts, (t, false))
|
||||
startBlock(p, "try {$n")
|
||||
expr(p, t[0], d)
|
||||
@@ -834,8 +834,12 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) =
|
||||
endBlock(p)
|
||||
else:
|
||||
for j in 0..t[i].len-2:
|
||||
assert(t[i][j].kind == nkType)
|
||||
startBlock(p, "catch ($1*) {$n", getTypeDesc(p.module, t[i][j].typ))
|
||||
if t[i][j].isInfixAs():
|
||||
let exvar = t[i][j][2] # ex1 in `except ExceptType as ex1:`
|
||||
fillLoc(exvar.sym.loc, locTemp, exvar, mangleLocalName(p, exvar.sym), OnUnknown)
|
||||
startBlock(p, "catch ($1& $2) {$n", getTypeDesc(p.module, t[i][j][1].typ), rdLoc(exvar.sym.loc))
|
||||
else:
|
||||
startBlock(p, "catch ($1&) {$n", getTypeDesc(p.module, t[i][j].typ))
|
||||
genExceptBranchBody(t[i][^1]) # exception handler body will duplicated for every type
|
||||
endBlock(p)
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
import sighashes
|
||||
from lowerings import createObj
|
||||
|
||||
proc genProcHeader(m: BModule, prc: PSym): Rope
|
||||
|
||||
proc isKeyword(w: PIdent): bool =
|
||||
# Nim and C++ share some keywords
|
||||
# it's more efficient to test the whole Nim keywords range
|
||||
@@ -573,7 +575,15 @@ proc getRecordDesc(m: BModule, typ: PType, name: Rope,
|
||||
appcg(m, result, " : public $1 {$n",
|
||||
[getTypeDescAux(m, typ.sons[0].skipTypes(skipPtrs), check)])
|
||||
if typ.isException:
|
||||
appcg(m, result, "virtual void raise() {throw this;}$n") # required for polymorphic exceptions
|
||||
appcg(m, result, "virtual void raise() {throw *this;}$n") # required for polymorphic exceptions
|
||||
if typ.sym.magic == mException:
|
||||
# Add cleanup destructor to Exception base class
|
||||
appcg(m, result, "~$1() {if(this->raise_id) popCurrentExceptionEx(this->raise_id);}$n", [name])
|
||||
# hack: forward declare popCurrentExceptionEx() on top of type description,
|
||||
# proper request to generate popCurrentExceptionEx not possible for 2 reasons:
|
||||
# generated function will be below declared Exception type and circular dependency
|
||||
# between Exception and popCurrentExceptionEx function
|
||||
result = genProcHeader(m, magicsys.getCompilerProc("popCurrentExceptionEx")) & ";" & rnl & result
|
||||
hasField = true
|
||||
else:
|
||||
appcg(m, result, " {$n $1 Sup;$n",
|
||||
|
||||
@@ -456,5 +456,3 @@ proc pickSym*(c: PContext, n: PNode; kinds: set[TSymKind];
|
||||
else: return nil # ambiguous
|
||||
a = nextOverloadIter(o, c, n)
|
||||
|
||||
proc isInfixAs*(n: PNode): bool =
|
||||
return n.kind == nkInfix and considerQuotedIdent(n[0]).s == "as"
|
||||
|
||||
@@ -359,9 +359,12 @@ proc trackTryStmt(tracked: PEffects, n: PNode) =
|
||||
catchesAll(tracked)
|
||||
else:
|
||||
for j in countup(0, blen - 2):
|
||||
assert(b.sons[j].kind == nkType)
|
||||
catches(tracked, b.sons[j].typ)
|
||||
|
||||
if b.sons[j].isInfixAs():
|
||||
assert(b.sons[j][1].kind == nkType)
|
||||
catches(tracked, b.sons[j][1].typ)
|
||||
else:
|
||||
assert(b.sons[j].kind == nkType)
|
||||
catches(tracked, b.sons[j].typ)
|
||||
setLen(tracked.init, oldState)
|
||||
track(tracked, b.sons[blen-1])
|
||||
for i in oldState..<tracked.init.len:
|
||||
|
||||
@@ -257,13 +257,19 @@ proc semCase(c: PContext, n: PNode): PNode =
|
||||
proc semTry(c: PContext, n: PNode): PNode =
|
||||
|
||||
var check = initIntSet()
|
||||
template semExceptBranchType(typeNode: PNode): PNode =
|
||||
template semExceptBranchType(typeNode: PNode): bool =
|
||||
# returns true if exception type is imported type
|
||||
let typ = semTypeNode(c, typeNode, nil).toObject()
|
||||
if typ.kind != tyObject:
|
||||
localError(typeNode.info, errExprCannotBeRaised)
|
||||
var is_imported = false
|
||||
if isImportedException(typ):
|
||||
is_imported = true
|
||||
elif not isException(typ):
|
||||
localError(typeNode.info, errExprCannotBeRaised)
|
||||
|
||||
if containsOrIncl(check, typ.id):
|
||||
localError(typeNode.info, errExceptionAlreadyHandled)
|
||||
newNodeIT(nkType, typeNode.info, typ)
|
||||
typeNode = newNodeIT(nkType, typeNode.info, typ)
|
||||
is_imported
|
||||
|
||||
result = n
|
||||
inc c.p.inTryStmt
|
||||
@@ -286,18 +292,24 @@ proc semTry(c: PContext, n: PNode): PNode =
|
||||
|
||||
if a.len == 2 and a[0].isInfixAs():
|
||||
# support ``except Exception as ex: body``
|
||||
a[0][1] = semExceptBranchType(a[0][1])
|
||||
|
||||
let is_imported = semExceptBranchType(a[0][1])
|
||||
let symbol = newSymG(skLet, a[0][2], c)
|
||||
symbol.typ = a[0][1].typ.toRef()
|
||||
symbol.typ = if is_imported: a[0][1].typ
|
||||
else: a[0][1].typ.toRef()
|
||||
addDecl(c, symbol)
|
||||
# Overwrite symbol in AST with the symbol in the symbol table.
|
||||
a[0][2] = newSymNode(symbol, a[0][2].info)
|
||||
|
||||
else:
|
||||
# support ``except KeyError, ValueError, ... : body``
|
||||
var is_native, is_imported: bool
|
||||
for j in 0..a.len-2:
|
||||
a[j] = semExceptBranchType(a[j])
|
||||
let tmp = semExceptBranchType(a[j])
|
||||
if tmp: is_imported = true
|
||||
else: is_native = true
|
||||
|
||||
if is_native and is_imported:
|
||||
localError(a[0].info, "Mix of imported and native exception types is not allowed in one except branch")
|
||||
|
||||
elif a.kind != nkFinally:
|
||||
illFormedAst(n)
|
||||
@@ -731,16 +743,19 @@ proc semFor(c: PContext, n: PNode): PNode =
|
||||
proc semRaise(c: PContext, n: PNode): PNode =
|
||||
result = n
|
||||
checkSonsLen(n, 1)
|
||||
if n.sons[0].kind != nkEmpty:
|
||||
n.sons[0] = semExprWithType(c, n.sons[0])
|
||||
var typ = n.sons[0].typ
|
||||
if typ.kind != tyRef or typ.lastSon.kind != tyObject:
|
||||
localError(n.info, errExprCannotBeRaised)
|
||||
if n[0].kind != nkEmpty:
|
||||
|
||||
n[0] = semExprWithType(c, n[0])
|
||||
let typ = n[0].typ
|
||||
|
||||
# check if the given object inherits from Exception
|
||||
if not typ.lastSon.isException():
|
||||
if not isImportedException(typ):
|
||||
|
||||
if typ.kind != tyRef or typ.lastSon.kind != tyObject:
|
||||
localError(n.info, errExprCannotBeRaised)
|
||||
|
||||
if not isException(typ.lastSon):
|
||||
localError(n.info, "raised object of type $1 does not inherit from Exception",
|
||||
[typeToString(typ)])
|
||||
[typeToString(typ)])
|
||||
|
||||
|
||||
proc addGenericParamListToScope(c: PContext, n: PNode) =
|
||||
|
||||
@@ -714,7 +714,7 @@ proc transformCall(c: PTransf, n: PNode): PTransNode =
|
||||
|
||||
proc transformExceptBranch(c: PTransf, n: PNode): PTransNode =
|
||||
result = transformSons(c, n)
|
||||
if n[0].isInfixAs():
|
||||
if n[0].isInfixAs() and (not isImportedException(n[0][1].typ)):
|
||||
let excTypeNode = n[0][1]
|
||||
let actions = newTransNode(nkStmtListExpr, n[1], 2)
|
||||
# Generating `let exc = (excType)(getCurrentException())`
|
||||
|
||||
@@ -156,3 +156,25 @@ Exception hierarchy
|
||||
The exception tree is defined in the `system <system.html>`_ module:
|
||||
|
||||
.. include:: ../exception_hierarchy_fragment.txt
|
||||
|
||||
|
||||
Imported exceptions
|
||||
-------------------
|
||||
|
||||
It is possible to raise/catch imported C++ exceptions. Types imported using
|
||||
`importcpp` can be raised or caught. Exceptions are raised by value and
|
||||
caught by reference. Example:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
type
|
||||
std_exception {.importcpp: "std::exception", header: "<exception>".} = object
|
||||
|
||||
proc what(s: std_exception): cstring {.importcpp: "((char *)#.what())".}
|
||||
|
||||
try:
|
||||
raise std_exception()
|
||||
except std_exception as ex:
|
||||
echo ex.what()
|
||||
|
||||
|
||||
@@ -483,6 +483,7 @@ type
|
||||
trace: string
|
||||
else:
|
||||
trace: seq[StackTraceEntry]
|
||||
raise_id: uint # set when exception is raised
|
||||
up: ref Exception # used for stacking exceptions. Not exported!
|
||||
|
||||
SystemError* = object of Exception ## \
|
||||
|
||||
@@ -33,6 +33,12 @@ proc showErrorMessage(data: cstring) {.gcsafe.} =
|
||||
else:
|
||||
writeToStdErr(data)
|
||||
|
||||
proc quitOrDebug() {.inline.} =
|
||||
when not defined(endb):
|
||||
quit(1)
|
||||
else:
|
||||
endbStep() # call the debugger
|
||||
|
||||
proc chckIndx(i, a, b: int): int {.inline, compilerproc, benign.}
|
||||
proc chckRange(i, a, b: int): int {.inline, compilerproc, benign.}
|
||||
proc chckRangeF(x, a, b: float): float {.inline, compilerproc, benign.}
|
||||
@@ -50,6 +56,8 @@ var
|
||||
# list of exception handlers
|
||||
# a global variable for the root of all try blocks
|
||||
currException {.threadvar.}: ref Exception
|
||||
raise_counter {.threadvar.}: uint
|
||||
|
||||
gcFramePtr {.threadvar.}: GcFrame
|
||||
|
||||
type
|
||||
@@ -108,6 +116,21 @@ proc pushCurrentException(e: ref Exception) {.compilerRtl, inl.} =
|
||||
proc popCurrentException {.compilerRtl, inl.} =
|
||||
currException = currException.up
|
||||
|
||||
proc popCurrentExceptionEx(id: uint) {.compilerRtl.} =
|
||||
# in cpp backend exceptions can pop-up in the different order they were raised, example #5628
|
||||
if currException.raise_id == id:
|
||||
currException = currException.up
|
||||
else:
|
||||
var cur = currException.up
|
||||
var prev = currException
|
||||
while cur != nil and cur.raise_id != id:
|
||||
prev = cur
|
||||
cur = cur.up
|
||||
if cur == nil:
|
||||
showErrorMessage("popCurrentExceptionEx() exception was not found in the exception stack. Aborting...")
|
||||
quitOrDebug()
|
||||
prev.up = cur.up
|
||||
|
||||
# some platforms have native support for stack traces:
|
||||
const
|
||||
nativeStackTraceSupported* = (defined(macosx) or defined(linux)) and
|
||||
@@ -291,12 +314,6 @@ when hasSomeStackTrace:
|
||||
else:
|
||||
proc stackTraceAvailable*(): bool = result = false
|
||||
|
||||
proc quitOrDebug() {.inline.} =
|
||||
when not defined(endb):
|
||||
quit(1)
|
||||
else:
|
||||
endbStep() # call the debugger
|
||||
|
||||
var onUnhandledException*: (proc (errorMsg: string) {.
|
||||
nimcall.}) ## set this error \
|
||||
## handler to override the existing behaviour on an unhandled exception.
|
||||
@@ -320,6 +337,10 @@ proc raiseExceptionAux(e: ref Exception) =
|
||||
quitOrDebug()
|
||||
else:
|
||||
pushCurrentException(e)
|
||||
raise_counter.inc
|
||||
if raise_counter == 0:
|
||||
raise_counter.inc # skip zero at overflow
|
||||
e.raise_id = raise_counter
|
||||
{.emit: "`e`->raise();".}
|
||||
else:
|
||||
if excHandler != nil:
|
||||
|
||||
134
tests/exception/tcpp_imported_exc.nim
Normal file
134
tests/exception/tcpp_imported_exc.nim
Normal file
@@ -0,0 +1,134 @@
|
||||
discard """
|
||||
targets: "cpp"
|
||||
output: '''caught as std::exception
|
||||
expected
|
||||
finally1
|
||||
finally2
|
||||
finally2
|
||||
2
|
||||
expected
|
||||
finally 1
|
||||
finally 2
|
||||
expected
|
||||
cpp exception caught
|
||||
'''
|
||||
"""
|
||||
|
||||
type
|
||||
std_exception* {.importcpp: "std::exception", header: "<exception>".} = object
|
||||
std_runtime_error* {.importcpp: "std::runtime_error", header: "<stdexcept>".} = object
|
||||
std_string* {.importcpp: "std::string", header: "<string>".} = object
|
||||
|
||||
proc constructStdString(s: cstring): std_string {.importcpp: "std::string(@)", constructor, header: "<string>".}
|
||||
|
||||
proc constructRuntimeError(s: stdstring): std_runtime_error {.importcpp: "std::runtime_error(@)", constructor.}
|
||||
|
||||
proc what(ex: std_runtime_error): cstring {.importcpp: "((char *)#.what())".}
|
||||
|
||||
proc myexception =
|
||||
raise constructRuntimeError(constructStdString("cpp_exception"))
|
||||
|
||||
try:
|
||||
myexception() # raise std::runtime_error
|
||||
except std_exception:
|
||||
echo "caught as std::exception"
|
||||
try:
|
||||
raise constructStdString("x")
|
||||
except std_exception:
|
||||
echo "should not happen"
|
||||
except:
|
||||
echo "expected"
|
||||
|
||||
doAssert(getCurrentException() == nil)
|
||||
|
||||
proc earlyReturn =
|
||||
try:
|
||||
try:
|
||||
myexception()
|
||||
finally:
|
||||
echo "finally1"
|
||||
except:
|
||||
return
|
||||
finally:
|
||||
echo "finally2"
|
||||
|
||||
earlyReturn()
|
||||
doAssert(getCurrentException() == nil)
|
||||
|
||||
|
||||
try:
|
||||
block blk1:
|
||||
try:
|
||||
raise newException(ValueError, "mmm")
|
||||
except:
|
||||
break blk1
|
||||
except:
|
||||
echo "should not happen"
|
||||
finally:
|
||||
echo "finally2"
|
||||
|
||||
doAssert(getCurrentException() == nil)
|
||||
|
||||
#--------------------------------------
|
||||
|
||||
# raise by pointer and also generic type
|
||||
|
||||
type
|
||||
std_vector {.importcpp"std::vector", header"<vector>".} [T] = object
|
||||
|
||||
proc newVector[T](len: int): ptr std_vector[T] {.importcpp: "new std::vector<'1>(@)".}
|
||||
proc deleteVector[T](v: ptr std_vector[T]) {.importcpp: "delete @; @ = NIM_NIL;".}
|
||||
proc len[T](v: std_vector[T]): uint {.importcpp: "size".}
|
||||
|
||||
var v = newVector[int](2)
|
||||
try:
|
||||
try:
|
||||
try:
|
||||
raise v
|
||||
except ptr std_vector[int] as ex:
|
||||
echo len(ex[])
|
||||
raise newException(ValueError, "msg5")
|
||||
except:
|
||||
echo "should not happen"
|
||||
finally:
|
||||
deleteVector(v)
|
||||
except:
|
||||
echo "expected"
|
||||
|
||||
doAssert(v == nil)
|
||||
doAssert(getCurrentException() == nil)
|
||||
|
||||
#--------------------------------------
|
||||
|
||||
# mix of Nim and imported exceptions
|
||||
try:
|
||||
try:
|
||||
try:
|
||||
raise newException(KeyError, "msg1")
|
||||
except KeyError:
|
||||
raise newException(ValueError, "msg2")
|
||||
except:
|
||||
echo "should not happen"
|
||||
finally:
|
||||
echo "finally 1"
|
||||
except:
|
||||
doAssert(getCurrentExceptionMsg() == "msg2")
|
||||
raise constructStdString("std::string")
|
||||
finally:
|
||||
echo "finally 2"
|
||||
except:
|
||||
echo "expected"
|
||||
|
||||
|
||||
doAssert(getCurrentException() == nil)
|
||||
|
||||
try:
|
||||
try:
|
||||
myexception()
|
||||
except std_runtime_error as ex:
|
||||
echo "cpp exception caught"
|
||||
raise newException(ValueError, "rewritten " & $ex.what())
|
||||
except:
|
||||
doAssert(getCurrentExceptionMsg() == "rewritten cpp_exception")
|
||||
|
||||
doAssert(getCurrentException() == nil)
|
||||
Reference in New Issue
Block a user