diff --git a/compiler/options.nim b/compiler/options.nim index 33f2ac57ab..48b5ffbc93 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -29,7 +29,7 @@ const nimEnableCovariance* = defined(nimEnableCovariance) - icFormatVersion* = "12" + icFormatVersion* = "14" ## Version of the IC cache format (the sem-NIF module layout written by ## ast2nif.nim plus the iface/impl/edges side files). Bump it whenever ## that layout changes: `commandIc` wipes a nimcache whose `ic.version` diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index 0d32930472..e1b37b21c6 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -1117,6 +1117,17 @@ proc trackCall(tracked: PEffects; n: PNode) = #if canRaise(a): # echo "this can raise ", tracked.config $ n.info let op = a.typ + # A routine whose body reaches a compile-time-only magic (`macros.error`, + # `slurp`, `gorge`, `getAst`, …) can never be code-generated — the C/JS + # backends reject those magics (ccgexprs `errXMustBeCompileTime`). Such a + # routine is compile-time-only by construction; mark it `sfCompileTime` so it + # is treated uniformly as such. Non-IC pruned it by demand-driven codegen, but + # the per-module IC backend emits every owned routine (no DCE) and would + # otherwise feed the magic to codegen. Mirrors the `tfTriggersCompileTime -> + # sfCompileTime` path in `semProcAux`. + if a.kind == nkSym and a.sym.magic in {mNLen..mNError, mSlurp..mQuoteAst} and + tracked.owner != nil and tracked.owner.kind in routineKinds: + incl(tracked.owner, sfCompileTime) if n.typ != nil: if tracked.owner.kind != skMacro and n.typ.skipTypes(abstractVar).kind != tyOpenArray: createTypeBoundOps(tracked, n.typ, n.info) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 94a38bd4f1..96ffb4eb40 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -2621,6 +2621,17 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, addParams(c, proto.typ.n, proto.kind) proto.info = s.info # more accurate line information proto.options = s.options + # `s` (the impl symbol) is discarded in favour of `proto`. It still carries + # `s.ast == n` (set above) and stays reachable as the owner of body-local + # symbols, so under IC it would be serialized as a SECOND, body-bearing + # `proc` entry — a phantom duplicate of `proto`. The per-module backend then + # codegens that phantom, whose `result` is owned by `proto` (addResult below + # re-parents it), not by the phantom: lambdalifting's capture check + # (`result.skipGenericOwner != owner`) then wrongly classifies `result` as a + # captured outer variable → "'result' … cannot be captured". Drop the + # discarded impl's body so it can never be emitted as a routine (same leak + # class the `miscPos` adoption below guards against for generic params). + let discardedImpl = s s = proto n[genericParamsPos] = proto.ast[genericParamsPos] n[paramsPos] = proto.ast[paramsPos] @@ -2638,6 +2649,7 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, if importantComments(c.config) and proto.ast.comment.len > 0: n.comment = proto.ast.comment proto.ast = n # needed for code generation + if discardedImpl != proto: discardedImpl.ast = nil popOwner(c) pushOwner(c, s)