fixes #26092; restore enclosing cast block state when a nested cast block exits (#26093)

`unapplyBlockContext` reset `inEnforcedGcSafe` and
`inEnforcedNoSideEffects` to false whenever a `{.cast(gcsafe).}` or
`{.cast(noSideEffect).}` block ended. When such a block is nested inside
another block of the same cast, the inner block's exit switched
enforcement back on for the rest of the enclosing block, so statements
lexically inside the outer cast were rejected.

The fix saves both flags in `PragmaBlockContext` when the block context
is created and restores the saved values on exit. That matches how every
other piece of block state there (`locked`, `exc`, `tags`, `forbids`) is
already handled; these two bools were the only ones reset to a constant
instead of restored.

No change for non-nested blocks: entering from the non-enforced state
saves false, so exit still clears the flag. A statement after the outer
block is still rejected as before.

Test covers nested `cast(gcsafe)` and nested `cast(noSideEffect)`.
`tests/effects` passes unchanged (49/49, same as stock).
This commit is contained in:
Corey Leavitt
2026-08-09 03:24:02 -06:00
committed by GitHub
parent f0e7969bb0
commit f4e8e04cd0
2 changed files with 39 additions and 2 deletions

View File

@@ -1345,6 +1345,7 @@ type
PragmaBlockContext = object
oldLocked: int
enforcedGcSafety, enforceNoSideEffects: bool
oldInEnforcedGcSafe, oldInEnforcedNoSideEffects: bool
oldExc, oldTags, oldForbids: int
exc, tags, forbids: PNode
excSource, tagsSource, forbidsSource: PNode
@@ -1354,6 +1355,8 @@ proc createBlockContext(tracked: PEffects): PragmaBlockContext =
if tracked.forbids != nil: oldForbidsLen = tracked.forbids.len
result = PragmaBlockContext(oldLocked: tracked.locked.len,
enforcedGcSafety: false, enforceNoSideEffects: false,
oldInEnforcedGcSafe: tracked.inEnforcedGcSafe,
oldInEnforcedNoSideEffects: tracked.inEnforcedNoSideEffects,
oldExc: tracked.exc.len, oldTags: tracked.tags.len,
oldForbids: oldForbidsLen)
@@ -1362,8 +1365,9 @@ proc applyBlockContext(tracked: PEffects, bc: PragmaBlockContext) =
if bc.enforceNoSideEffects: tracked.inEnforcedNoSideEffects = true
proc unapplyBlockContext(tracked: PEffects; bc: PragmaBlockContext) =
if bc.enforcedGcSafety: tracked.inEnforcedGcSafe = false
if bc.enforceNoSideEffects: tracked.inEnforcedNoSideEffects = false
if bc.enforcedGcSafety: tracked.inEnforcedGcSafe = bc.oldInEnforcedGcSafe
if bc.enforceNoSideEffects:
tracked.inEnforcedNoSideEffects = bc.oldInEnforcedNoSideEffects
setLen(tracked.locked, bc.oldLocked)
if bc.exc != nil:
# beware that 'raises: []' is very different from not saying

View File

@@ -0,0 +1,33 @@
discard """
output: '''4
3'''
"""
# bug #26092: exiting a nested cast block used to cancel the enclosing
# block's cast for the statements after it.
type Obj = ref object
x: int
let g: Obj = Obj(x: 1)
var fp: proc(): int {.nimcall.} = proc(): int {.nimcall.} = 2
proc consumer(): int {.gcsafe.} =
{.cast(gcsafe).}:
let a = g.x
var b: int
{.cast(gcsafe).}:
b = g.x
let c = fp()
a + b + c
echo consumer()
var counter = 0
proc bump() = inc counter
proc pure(): int {.noSideEffect.} =
{.cast(noSideEffect).}:
bump()
{.cast(noSideEffect).}:
bump()
bump()
counter
echo pure()