From cfa769fefc86c2f51f5647605622e19a349dd7ee Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Thu, 28 May 2026 05:28:27 +0800 Subject: [PATCH 1/6] fixes #22950; Poor error message on cast effect violation (#25839) fixes #22950 This pull request improves the tracking and reporting of effect annotations (such as `raises`, `tags`, and `forbids`) in pragma blocks, particularly when using the `cast` pragma. It ensures that the source of these effect annotations is correctly preserved and referenced, which improves error reporting and effect analysis. Additionally, a new test was added to check for violations when using `cast` with effect annotations. Effect annotation source tracking and propagation: * Added new fields (`excSource`, `tagsSource`, `forbidsSource`) to the `PragmaBlockContext` type to store the original source node for each effect annotation. * Updated `castBlock` to set these new source fields when processing `raises`, `tags`, and `forbids` pragmas, ensuring the source node is preserved for later error reporting. * Modified `unapplyBlockContext` to use the stored source node (if available) when calling `addRaiseEffect`, `addTag`, and `addNotTag`, improving the accuracy of effect tracking and diagnostics. Pragma handling improvements: * Changed the call to `castBlock` in the main pragma processing loop to pass the entire pragma node, enabling access to the original source for effect annotations. Testing: * Added a new test (`tests/effects/tcast_effect_violation.nim`) to verify that using `cast(raises: ValueError)` inside a procedure with `.raises: [].` correctly triggers an error message about an unlisted exception. --- compiler/sempass2.nim | 15 ++++++++++----- tests/effects/tcast_effect_violation.nim | 8 ++++++++ 2 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 tests/effects/tcast_effect_violation.nim diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index 9c84b721ad..f1e2e69cbe 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -1208,6 +1208,7 @@ type enforcedGcSafety, enforceNoSideEffects: bool oldExc, oldTags, oldForbids: int exc, tags, forbids: PNode + excSource, tagsSource, forbidsSource: PNode proc createBlockContext(tracked: PEffects): PragmaBlockContext = var oldForbidsLen = 0 @@ -1230,17 +1231,18 @@ proc unapplyBlockContext(tracked: PEffects; bc: PragmaBlockContext) = # anything about 'raises' in the 'cast' at all. Same applies for 'tags'. setLen(tracked.exc.sons, bc.oldExc) for e in bc.exc: - addRaiseEffect(tracked, e, e) + addRaiseEffect(tracked, e, if bc.excSource != nil: bc.excSource else: e) if bc.tags != nil: setLen(tracked.tags.sons, bc.oldTags) for t in bc.tags: - addTag(tracked, t, t) + addTag(tracked, t, if bc.tagsSource != nil: bc.tagsSource else: t) if bc.forbids != nil: setLen(tracked.forbids.sons, bc.oldForbids) for t in bc.forbids: - addNotTag(tracked, t, t) + addNotTag(tracked, t, if bc.forbidsSource != nil: bc.forbidsSource else: t) -proc castBlock(tracked: PEffects, pragma: PNode, bc: var PragmaBlockContext) = +proc castBlock(tracked: PEffects, castPragma: PNode, bc: var PragmaBlockContext) = + let pragma = castPragma[1] case whichPragma(pragma) of wGcSafe: bc.enforcedGcSafety = true @@ -1253,6 +1255,7 @@ proc castBlock(tracked: PEffects, pragma: PNode, bc: var PragmaBlockContext) = else: bc.tags = newNodeI(nkArgList, pragma.info) bc.tags.add n + bc.tagsSource = castPragma of wForbids: let n = pragma[1] if n.kind in {nkCurly, nkBracket}: @@ -1260,6 +1263,7 @@ proc castBlock(tracked: PEffects, pragma: PNode, bc: var PragmaBlockContext) = else: bc.forbids = newNodeI(nkArgList, pragma.info) bc.forbids.add n + bc.forbidsSource = castPragma of wRaises: let n = pragma[1] if n.kind in {nkCurly, nkBracket}: @@ -1267,6 +1271,7 @@ proc castBlock(tracked: PEffects, pragma: PNode, bc: var PragmaBlockContext) = else: bc.exc = newNodeI(nkArgList, pragma.info) bc.exc.add n + bc.excSource = castPragma of wUncheckedAssign: discard "handled in sempass1" else: @@ -1520,7 +1525,7 @@ proc track(tracked: PEffects, n: PNode) = of wNoSideEffect: bc.enforceNoSideEffects = true of wCast: - castBlock(tracked, pragmaList[i][1], bc) + castBlock(tracked, pragmaList[i], bc) else: discard applyBlockContext(tracked, bc) diff --git a/tests/effects/tcast_effect_violation.nim b/tests/effects/tcast_effect_violation.nim new file mode 100644 index 0000000000..fe7f93cc4a --- /dev/null +++ b/tests/effects/tcast_effect_violation.nim @@ -0,0 +1,8 @@ +discard """ + errormsg: "cast(raises: ValueError) can raise an unlisted exception: ValueError" + line: 7 +""" + +proc fff() {.raises: [].} = + {.cast(raises: ValueError).}: + discard \ No newline at end of file From 3e2cea21ed3f77320a214539deaf02818e0ba21c Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Thu, 28 May 2026 05:29:27 +0800 Subject: [PATCH 2/6] fixes #22791; ProveField warning with nested case object (#25774) fixes #22791 This pull request introduces a minor improvement to the handling of immutable variables in the compiler and adds a new test case for nested case objects. The most important changes are: ### Compiler improvements * Updated the `isLet` guard in `compiler/guards.nim` to recognize `skConst` symbols as immutable variables, ensuring that constants are correctly identified alongside lets and other immutable types. ### Test coverage * Added a new test in `tests/objvariant/tcorrectcheckedfield.nim` for bug #22791, verifying correct pattern matching and field access in nested `case` objects with constants. --- compiler/guards.nim | 2 +- tests/objvariant/tcorrectcheckedfield.nim | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/compiler/guards.nim b/compiler/guards.nim index 553cc744df..fcfafc954a 100644 --- a/compiler/guards.nim +++ b/compiler/guards.nim @@ -46,7 +46,7 @@ proc isLocation(n: PNode): bool = not n.isValue proc isLet(n: PNode): bool = if n.kind == nkSym: - if n.sym.kind in {skLet, skTemp, skForVar}: + if n.sym.kind in {skLet, skConst, skTemp, skForVar}: # guard immutable variables result = true elif n.sym.kind == skParam and skipTypes(n.sym.typ, abstractInst).kind notin {tyVar}: diff --git a/tests/objvariant/tcorrectcheckedfield.nim b/tests/objvariant/tcorrectcheckedfield.nim index e5e67c727d..acb3acda14 100644 --- a/tests/objvariant/tcorrectcheckedfield.nim +++ b/tests/objvariant/tcorrectcheckedfield.nim @@ -20,3 +20,25 @@ block: # issue #24021 discard else: discard foo.z + + +# bug #22791 +type Foo = object + case a: bool + of false: + discard + of true: + case b: bool + of false: + discard + of true: + c: bool + +const f = Foo(a: true, b: true, c: true) +case f.a +of true: + case f.b + of true: + echo f.c + else: discard +else: discard \ No newline at end of file From f4dd00c4ccfc9791ee21a6d93a18846646613ae6 Mon Sep 17 00:00:00 2001 From: Antonis Geralis <43617260+planetis-m@users.noreply.github.com> Date: Thu, 28 May 2026 00:31:39 +0300 Subject: [PATCH 3/6] Scan until next special char (", \, \0, \c, \L) and append that slice once. (#25498) Benchmark comparison (-d:danger --mm:arc --debugger:native -d:useMalloc, OpenAI file benchmark, 5 runs): - Before: 0.196674934, 0.189423191, 0.198763300, 0.197125584, 0.205015032 - After: 0.182827130, 0.183330852, 0.174878542, 0.174360811, 0.181704921 - Median before: 0.197125584s - Median after: 0.181704921s - Improvement: 7.82% faster Callgrind comparison (same build flags): - Total Ir before: 3,219,477,120 - Total Ir after: 2,449,556,167 - Total Ir reduction: 23.91% parseString hotspot: - Before: 1,343,343,723 Ir - After: 573,423,735 Ir - Reduction: 57.31% --- lib/pure/parsejson.nim | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/lib/pure/parsejson.nim b/lib/pure/parsejson.nim index 9292a85964..657f8cc697 100644 --- a/lib/pure/parsejson.nim +++ b/lib/pure/parsejson.nim @@ -175,23 +175,48 @@ proc parseEscapedUTF16*(buf: cstring, pos: var int): int = else: return -1 +proc addSpan(dst: var string; src: string; startPos, endPos: int) {.inline.} = + let n = endPos - startPos + if n <= 0: + return + + let old = dst.len + dst.setLen old + n + + template impl = + for i in 0.. Date: Fri, 29 May 2026 14:53:37 +0900 Subject: [PATCH 4/6] fixes ReraiseDefect after typeless `except:` + `finally:` (cpp backend) (#25777) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Bug A bare `except:` followed by a `finally:` block raises a spurious `ReraiseDefect: no exception to reraise` when compiled with `nim cpp`: ```nim proc test() = try: raise newException(CatchableError, "x") except: discard finally: echo "finally" test() echo "after" ``` Expected output: ``` finally after ``` Actual output: ``` finally fatal.nim(53) sysFatal Error: unhandled exception: no exception to reraise [ReraiseDefect] ``` This reproduces on every memory manager (`--mm:arc`, `--mm:orc`, `--mm:refc`). ## Root cause `genTryCpp` emits `try { ... } catch (Exception* T_) { ... }` followed by a finally block that ends with `if (T_) std::rethrow_exception(T_);`. In the *typed* except branches the codegen explicitly sets `T_ = nullptr;` once the exception is handled, so the rethrow check in the finally is a no-op. The typeless `except:` branch (the `if t[i].len == 1` arm) emitted only `popCurrentException()` and forgot to clear `T_`. After the handler body finished, `T_` still pointed at the original exception, so the trailing `if (T_) std::rethrow_exception(T_);` rethrew it. By that point Nim's current-exception stack had already been popped, and the rethrow surfaced as `ReraiseDefect`. ## Fix Emit `T_ = nullptr;` at the start of the typeless `except:` handler body, mirroring what is already done for the typed branches. This is the same one-line treatment that fixed the analogous typed-except case for #5871. ## Tests Adds `tests/exception/treraise_typeless_except_finally.nim`, exercising the bug pattern on `--mm:arc`, `--mm:orc`, and `--mm:refc`. Locally: - `tests/exception/` — 43 PASS, 0 FAIL, 3 SKIP - new test passes on all three memory managers ## Backport Tagged `[backport]` in the commit message — the same bug exists in `version-2-2` and the fix applies cleanly there. ## Related Independent of, but in the same family as, #25775 (also currently open). Both are silent-finally / cpp-backend exception handling fixes; they touch different lines of `genTryCpp` and don't conflict. Co-authored-by: puffball1567 <17452514+puffball1567@users.noreply.github.com> --- compiler/ccgstmts.nim | 1 + .../treraise_typeless_except_finally.nim | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 tests/exception/treraise_typeless_except_finally.nim diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim index 83f5e90172..bc9c06fa1d 100644 --- a/compiler/ccgstmts.nim +++ b/compiler/ccgstmts.nim @@ -1237,6 +1237,7 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) = else: scope = initScope(p.s(cpsStmts)) # we handled the error: + linefmt(p, cpsStmts, "T$1_ = nullptr;$n", [etmp]) expr(p, t[i][0], d) linefmt(p, cpsStmts, "#popCurrentException();$n", []) endBlockWith(p): diff --git a/tests/exception/treraise_typeless_except_finally.nim b/tests/exception/treraise_typeless_except_finally.nim new file mode 100644 index 0000000000..259863e4d4 --- /dev/null +++ b/tests/exception/treraise_typeless_except_finally.nim @@ -0,0 +1,30 @@ +discard """ + targets: "cpp" + matrix: "--mm:arc; --mm:orc; --mm:refc" + output: ''' +finally +after +''' +""" + +# Regression test: typeless `except:` followed by `finally:` must not +# trigger ReraiseDefect at the end of the proc. +# +# Previously, `genTryCpp` only emitted `T_ = nullptr;` in the *typed* +# except branches, leaving the typeless `except:` path with a still-set +# `T_`. After the handler body and `popCurrentException`, the trailing +# `if (T_) std::rethrow_exception(T_);` in the finally block would still +# fire — but with the Nim exception stack already popped, the rethrow +# bubbled up as a `ReraiseDefect: no exception to reraise`. + +proc test() = + try: + raise newException(CatchableError, "x") + except: + let e = getCurrentException() + discard e + finally: + echo "finally" + +test() +echo "after" From 645e13173942999ed98c1e545ae241af44b1da50 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Fri, 29 May 2026 13:58:23 +0800 Subject: [PATCH 5/6] fixes #25796; fixes procParamTypeRel to ensure backend type consistency (#25798) fixes #25796 This pull request addresses a subtle type-matching issue in the Nim compiler related to backend type compatibility, particularly for procedures returning `lent` types. It also adds new test cases to ensure correct handling of these scenarios. **Compiler type-checking fix:** * Updated `procParamTypeRel` in `compiler/sigmatch.nim` to skip wrappers like `tyVar`, `tyLent`, `tySink`, and `tyOwned` before comparing backend types, ensuring more accurate type equivalence checks for procedure parameters and return types. **Test coverage improvements:** * Added multiple blocks in `tests/proc/tproc.nim` to test procedure types returning `lent` objects, including cases with constants, variables, and union parameter types, verifying that the compiler now correctly handles these cases. --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- compiler/sigmatch.nim | 4 +++- compiler/types.nim | 3 ++- tests/proc/tproc.nim | 27 +++++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index bf9c2d2050..db06d3e427 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -791,8 +791,10 @@ proc procParamTypeRel(c: var TCandidate; f, a: PType): TTypeRelation = # different C types (size_t vs unsigned long long). let fCheck = concreteType(c, f) let aCheck = concreteType(c, a) + # Note that `result` is equal; now check whether they have the same + # backend type. if fCheck != nil and aCheck != nil and - not sameBackendTypePickyAliases(fCheck, aCheck): + not sameBackendTypePickyAliases(fCheck, aCheck, {IgnoreFlags}): result = isNone if result <= isSubrange or inconsistentVarTypes(f, a): diff --git a/compiler/types.nim b/compiler/types.nim index de24471e7d..f4c7e74bc5 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -1069,9 +1069,10 @@ proc sameBackendTypeIgnoreRange*(x, y: PType): bool = c.cmp = dcEqIgnoreDistinct result = sameTypeAux(x, y, c) -proc sameBackendTypePickyAliases*(x, y: PType): bool = +proc sameBackendTypePickyAliases*(x, y: PType, flags: TTypeCmpFlags = {}): bool = var c = initSameTypeClosure() c.flags.incl {IgnoreTupleFields, IgnoreRangeShallow, PickyCAliases, PickyBackendAliases} + c.flags.incl flags c.cmp = dcEqIgnoreDistinct result = sameTypeAux(x, y, c) diff --git a/tests/proc/tproc.nim b/tests/proc/tproc.nim index d7f8619917..564f9be31b 100644 --- a/tests/proc/tproc.nim +++ b/tests/proc/tproc.nim @@ -29,3 +29,30 @@ block tnestprc: result = x + y result = add(x, 3) doAssert Add3(7) == 10 + +block: + type A = object + c: int + type H = proc(): lent A {.nimcall.} + const u = A(c: 0) + proc e(T: typedesc): lent A = u + proc y(T: typedesc): H = + proc(): lent A {.nimcall.} = T.e + discard y(int) + +block: + type A = object + c: int + type H = proc(): lent A {.nimcall.} + let u = A(c: 0) + proc y(_: int | int): H = + proc(): lent A {.nimcall.} = u + discard y(0) + +block: + type A = object + c: int + type H = proc(): lent A {.nimcall.} + let u = A() + let _: H = proc(): lent A {.nimcall.} = u + From 7813bd8b92824cacec9cddb5152f8c9ed645e03c Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Fri, 29 May 2026 08:08:42 +0200 Subject: [PATCH 6/6] fixes #25693 (#25842) --- compiler/sigmatch.nim | 4 +++ tests/template/toverload_over_untyped.nim | 32 +++++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 tests/template/toverload_over_untyped.nim diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index db06d3e427..dd5944899b 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -2473,6 +2473,10 @@ proc paramTypesMatchAux(m: var TCandidate, f, a: PType, return arg elif f.kind == tyStatic and arg.typ.n != nil: return arg.typ.n + elif f.kind == tyUntyped: + # bug #25693: a different overload candidate may have sem-checked the + # operand and left symbols behind; templates expect the pristine AST. + return argOrig else: return argSemantized # argOrig diff --git a/tests/template/toverload_over_untyped.nim b/tests/template/toverload_over_untyped.nim new file mode 100644 index 0000000000..0f734480d6 --- /dev/null +++ b/tests/template/toverload_over_untyped.nim @@ -0,0 +1,32 @@ +discard """ + output: "ok" +""" + +# bug #25693 + +template g(b: untyped) {.dirty.} = + template t: untyped = b + +proc d() = discard @[0] +proc g(_: int) = discard + +proc f(a: var seq[int], _: string) = + let p = @[0] + d() + a = p + +let q = "a" +g: + var a: seq[int] + try: + f(a, q & "1") + except CatchableError: + discard + try: + f(a, q & "1") + except CatchableError: + discard +block: t() +block: t() +echo "ok" +