fixes #22122; Unclear error message for raise of a complex expression (#25899)

fixes  #22122

The commit fixes a bug in Nim's effects checker where raise statements
with case/if expressions (commonly from template expansion) failed to
track exception types from individual branches.
Problem: addRaiseEffect only saw the outermost expression. When a
template like getTransportError(err) expanded to a case expression
raising 3 different exception types, the compiler only registered the
top-level call — missing the branch-level exceptions.
Fix (2 files):
- compiler/sempass2.nim: Added skipHiddenConv to strip implicit type
coercion nodes (nkHiddenStdConv/nkHiddenSubConv) that hide the control
flow structure. Added addRaiseEffectsFromExpr that recursively walks
into case/if/block/stmtlist expressions to find raise effects in each
branch body. Changed the nkRaiseStmt handler to use this new function.
- tests/effects/tcase_raises.nim: Test with templates that expand to
case expressions raising different exception types, verified via
{.raises: [].} pragma.
This commit is contained in:
ringabout
2026-06-13 16:17:59 +08:00
committed by GitHub
parent 8ad1d106ec
commit 587f90a816
2 changed files with 102 additions and 1 deletions

View File

@@ -497,6 +497,33 @@ proc addRaiseEffect(a: PEffects, e, comesFrom: PNode) =
if not isDefectException(e.typ):
throws(a.exc, e, comesFrom)
proc skipHiddenConv(n: PNode): PNode =
result = n
while true:
case result.kind
of nkHiddenStdConv, nkHiddenSubConv:
result = result[1]
else: break
proc addRaiseEffectsFromExpr(a: PEffects, e, comesFrom: PNode) =
if e.isNil:
return
case e.kind
of nkStmtList, nkStmtListExpr, nkBlockStmt, nkBlockExpr:
if e.len > 0:
addRaiseEffectsFromExpr(a, e.lastSon.skipHiddenConv, comesFrom)
of nkIfExpr, nkIfStmt:
for branch in items(e):
if branch.len > 0:
addRaiseEffectsFromExpr(a, branch.lastSon.skipHiddenConv, comesFrom)
of nkCaseStmt:
for i in 1..<e.len:
let branch = e[i]
if branch.len > 0:
addRaiseEffectsFromExpr(a, branch.lastSon.skipHiddenConv, comesFrom)
else:
addRaiseEffect(a, e, comesFrom)
proc addTag(a: PEffects, e, comesFrom: PNode) =
var aa = a.tags
for i in 0..<aa.len:
@@ -1326,7 +1353,7 @@ proc track(tracked: PEffects, n: PNode) =
if n[0].kind != nkEmpty:
n[0].info = n.info
#throws(tracked.exc, n[0])
addRaiseEffect(tracked, n[0], n)
addRaiseEffectsFromExpr(tracked, n[0], n)
for i in 0..<n.safeLen:
track(tracked, n[i])
createTypeBoundOps(tracked, n[0].typ, n.info)

View File

@@ -0,0 +1,74 @@
from std/os import osLastError, osErrorMsg, OSErrorCode, raiseOSError,
newOSError, `==`
{.push raises: [].}
const
EPERM* = OSErrorCode(1)
ECONNABORTED* = OSErrorCode(53)
ETIMEDOUT* = OSErrorCode(60)
ENOTCONN* = OSErrorCode(107)
EMFILE* = OSErrorCode(24)
ENFILE* = OSErrorCode(23)
ENOBUFS* = OSErrorCode(55)
ENOMEM* = OSErrorCode(12)
type
AsyncError* = object of CatchableError
TransportErrorBase* = object of AsyncError
TransportOsError* = object of TransportErrorBase
code*: OSErrorCode
TransportTooManyError* = object of TransportErrorBase
TransportAbortedError* = object of TransportErrorBase
template getConnectionAbortedError*(
code: OSErrorCode
): ref TransportAbortedError =
let msg =
case code
of OSErrorCode(0), ECONNABORTED:
"[ECONNABORTED] Connection has been aborted before being accepted"
of EPERM:
"[EPERM] Firewall rules forbid connection"
of ETIMEDOUT:
"[ETIMEDOUT] Operation has been timed out"
of ENOTCONN:
"[ENOTCONN] Transport endpoint is not connected"
else:
"[" & $int(code) & "] Connection has been aborted"
newException(TransportAbortedError, msg)
template getTransportTooManyError*(
code = OSErrorCode(0)
): ref TransportTooManyError =
let msg =
case code
of OSErrorCode(0):
"Too many open transports"
of EMFILE:
"[EMFILE] Too many open files in the process"
of ENFILE:
"[ENFILE] Too many open files in system"
of ENOBUFS:
"[ENOBUFS] No buffer space available"
of ENOMEM:
"[ENOMEM] Not enough memory availble"
else:
"[" & $int(code) & "] Too many open transports"
newException(TransportTooManyError, msg)
template getTransportError*(ecode: OSErrorCode): untyped =
case ecode
of ECONNABORTED, EPERM, ETIMEDOUT, ENOTCONN:
getConnectionAbortedError(ecode)
of EMFILE, ENFILE, ENOBUFS, ENOMEM:
getTransportTooManyError(ecode)
else:
(ref TransportOsError)(code: ecode,
msg: "(" & $int(ecode) & ") " & osErrorMsg(ecode))
proc raiseTransportError*(err: OSErrorCode) {.
raises: [TransportAbortedError, TransportTooManyError, TransportOsError],
noreturn.} =
## Raises transport specific OS error.
raise getTransportError(err)