strict effects (#18777)

* fixes #17369
* megatest is green for --cpu:arm64
* docgen output includes more tags/raises
* implemented 'effectsOf' 
* algorithm.nim: uses new effectsOf annotation
* closes #18376
* closes #17475
* closes #13905
* allow effectsOf: [a, b]
* added a test case
* parameters that are not ours cannot be declared as .effectsOf
* documentation
* manual: added the 'sort' example
* bootstrap with the new better options
This commit is contained in:
Andreas Rumpf 2021-09-02 12:10:14 +02:00 • committed by GitHub
commit e0ef859130
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 482 additions and 203 deletions

View file

@ -427,6 +427,23 @@
- On embedded devices `malloc` can now be used instead of `mmap` via `-d:nimAllocPagesViaMalloc`. - On embedded devices `malloc` can now be used instead of `mmap` via `-d:nimAllocPagesViaMalloc`.
This is only supported for `--gc:orc` or `--gc:arc`. This is only supported for `--gc:orc` or `--gc:arc`.
- The effect system was refined and there is a new `.effectsOf` annotation that does
explicitly what was previously done implicitly. See the manual for details.
To write code that is portable with older Nim versions, use this idiom:
```nim
when defined(nimHasEffectsOf):
{.experimental: "strictEffects".}
else:
{.pragma: effectsOf.}
proc mysort(s: seq; cmp: proc(a, b: T): int) {.effectsOf: cmp.}
```
To enable the new effect system, use --experimental:strictEffects.
## Compiler changes ## Compiler changes

View file

@ -229,7 +229,7 @@ type
TNodeKinds* = set[TNodeKind] TNodeKinds* = set[TNodeKind]
type type
TSymFlag* = enum # 47 flags! TSymFlag* = enum # 48 flags!
sfUsed, # read access of sym (for warnings) or simply used sfUsed, # read access of sym (for warnings) or simply used
sfExported, # symbol is exported from module sfExported, # symbol is exported from module
sfFromGeneric, # symbol is instantiation of a generic; this is needed sfFromGeneric, # symbol is instantiation of a generic; this is needed
@ -299,6 +299,7 @@ type
sfUsedInFinallyOrExcept # symbol is used inside an 'except' or 'finally' sfUsedInFinallyOrExcept # symbol is used inside an 'except' or 'finally'
sfSingleUsedTemp # For temporaries that we know will only be used once sfSingleUsedTemp # For temporaries that we know will only be used once
sfNoalias # 'noalias' annotation, means C's 'restrict' sfNoalias # 'noalias' annotation, means C's 'restrict'
sfEffectsDelayed # an 'effectsDelayed' parameter
TSymFlags* = set[TSymFlag] TSymFlags* = set[TSymFlag]
@ -568,6 +569,7 @@ type
# sizeof, alignof, offsetof at CT # sizeof, alignof, offsetof at CT
tfExplicitCallConv tfExplicitCallConv
tfIsConstructor tfIsConstructor
tfEffectSystemWorkaround
TTypeFlags* = set[TTypeFlag] TTypeFlags* = set[TTypeFlag]
@ -1781,7 +1783,7 @@ proc containsNode*(n: PNode, kinds: TNodeKinds): bool =
proc hasSubnodeWith*(n: PNode, kind: TNodeKind): bool = proc hasSubnodeWith*(n: PNode, kind: TNodeKind): bool =
case n.kind case n.kind
of nkEmpty..nkNilLit: result = n.kind == kind of nkEmpty..nkNilLit, nkFormalParams: result = n.kind == kind
else: else:
for i in 0..<n.len: for i in 0..<n.len:
if (n[i].kind == kind) or hasSubnodeWith(n[i], kind): if (n[i].kind == kind) or hasSubnodeWith(n[i], kind):

View file

@ -325,7 +325,7 @@ proc skipTrivialIndirections(n: PNode): PNode =
proc getPotentialWrites(n: PNode; mutate: bool; result: var seq[PNode]) = proc getPotentialWrites(n: PNode; mutate: bool; result: var seq[PNode]) =
case n.kind: case n.kind:
of nkLiterals, nkIdent: discard of nkLiterals, nkIdent, nkFormalParams: discard
of nkSym: of nkSym:
if mutate: result.add n if mutate: result.add n
of nkAsgn, nkFastAsgn: of nkAsgn, nkFastAsgn:
@ -354,7 +354,7 @@ proc getPotentialWrites(n: PNode; mutate: bool; result: var seq[PNode]) =
proc getPotentialReads(n: PNode; result: var seq[PNode]) = proc getPotentialReads(n: PNode; result: var seq[PNode]) =
case n.kind: case n.kind:
of nkLiterals, nkIdent: discard of nkLiterals, nkIdent, nkFormalParams: discard
of nkSym: result.add n of nkSym: result.add n
else: else:
for s in n: for s in n:

View file

@ -8,7 +8,6 @@
# #
# This module handles the parsing of command line arguments. # This module handles the parsing of command line arguments.
from ast import setUseIc
# We do this here before the 'import' statement so 'defined' does not get # We do this here before the 'import' statement so 'defined' does not get
# confused with 'TGCMode.gcMarkAndSweep' etc. # confused with 'TGCMode.gcMarkAndSweep' etc.
@ -30,7 +29,7 @@ import
msgs, options, nversion, condsyms, extccomp, platform, msgs, options, nversion, condsyms, extccomp, platform,
wordrecg, nimblecmd, lineinfos, pathutils, pathnorm wordrecg, nimblecmd, lineinfos, pathutils, pathnorm
from ast import eqTypeFlags, tfGcSafe, tfNoSideEffect from ast import setUseIc, eqTypeFlags, tfGcSafe, tfNoSideEffect
# but some have deps to imported modules. Yay. # but some have deps to imported modules. Yay.
bootSwitch(usedTinyC, hasTinyCBackend, "-d:tinyc") bootSwitch(usedTinyC, hasTinyCBackend, "-d:tinyc")

View file

@ -137,3 +137,4 @@ proc initDefines*(symbols: StringTableRef) =
defineSymbol("nimHasDragonBox") defineSymbol("nimHasDragonBox")
defineSymbol("nimHasHintAll") defineSymbol("nimHasHintAll")
defineSymbol("nimHasTrace") defineSymbol("nimHasTrace")
defineSymbol("nimHasEffectsOf")

View file

@ -1085,10 +1085,11 @@ proc documentEffect(cache: IdentCache; n, x: PNode, effectType: TSpecialWord, id
let actual = s.typ.n[0] let actual = s.typ.n[0]
if actual.len != effectListLen: return if actual.len != effectListLen: return
let real = actual[idx] let real = actual[idx]
if real == nil: return
let realLen = real.len
# warning: hack ahead: # warning: hack ahead:
var effects = newNodeI(nkBracket, n.info, real.len) var effects = newNodeI(nkBracket, n.info, realLen)
for i in 0..<real.len: for i in 0..<realLen:
var t = typeToString(real[i].typ) var t = typeToString(real[i].typ)
if t.startsWith("ref "): t = substr(t, 4) if t.startsWith("ref "): t = substr(t, 4)
effects[i] = newIdentNode(getIdent(cache, t), n.info) effects[i] = newIdentNode(getIdent(cache, t), n.info)

View file

@ -75,6 +75,7 @@ type
warnAnyEnumConv = "AnyEnumConv", warnAnyEnumConv = "AnyEnumConv",
warnHoleEnumConv = "HoleEnumConv", warnHoleEnumConv = "HoleEnumConv",
warnCstringConv = "CStringConv", warnCstringConv = "CStringConv",
warnEffect = "Effect",
warnUser = "User", warnUser = "User",
# hints # hints
hintSuccess = "Success", hintSuccessX = "SuccessX", hintSuccess = "Success", hintSuccessX = "SuccessX",
@ -163,6 +164,7 @@ const
warnAnyEnumConv: "$1", warnAnyEnumConv: "$1",
warnHoleEnumConv: "$1", warnHoleEnumConv: "$1",
warnCstringConv: "$1", warnCstringConv: "$1",
warnEffect: "$1",
warnUser: "$1", warnUser: "$1",
hintSuccess: "operation successful: $#", hintSuccess: "operation successful: $#",
# keep in sync with `testament.isSuccess` # keep in sync with `testament.isSuccess`

View file

@ -25,3 +25,8 @@ define:useStdoutAsStdmsg
@if nimHasWarningObservableStores: @if nimHasWarningObservableStores:
warning:ObservableStores: off warning:ObservableStores: off
@end @end
@if nimHasEffectsOf:
experimental:strictEffects
warningAsError:Effect:on
@end

View file

@ -204,7 +204,8 @@ type
strictFuncs, strictFuncs,
views, views,
strictNotNil, strictNotNil,
overloadableEnums overloadableEnums,
strictEffects
LegacyFeature* = enum LegacyFeature* = enum
allowSemcheckedAstModification, allowSemcheckedAstModification,

View file

@ -29,7 +29,7 @@ const
wCompilerProc, wNonReloadable, wCore, wProcVar, wVarargs, wCompileTime, wMerge, wCompilerProc, wNonReloadable, wCore, wProcVar, wVarargs, wCompileTime, wMerge,
wBorrow, wImportCompilerProc, wThread, wBorrow, wImportCompilerProc, wThread,
wAsmNoStackFrame, wDiscardable, wNoInit, wCodegenDecl, wAsmNoStackFrame, wDiscardable, wNoInit, wCodegenDecl,
wGensym, wInject, wRaises, wTags, wLocks, wDelegator, wGcSafe, wGensym, wInject, wRaises, wEffectsOf, wTags, wLocks, wDelegator, wGcSafe,
wConstructor, wLiftLocals, wStackTrace, wLineTrace, wNoDestroy, wConstructor, wLiftLocals, wStackTrace, wLineTrace, wNoDestroy,
wRequires, wEnsures} wRequires, wEnsures}
converterPragmas* = procPragmas converterPragmas* = procPragmas
@ -41,7 +41,7 @@ const
wDiscardable, wGensym, wInject, wDelegator} wDiscardable, wGensym, wInject, wDelegator}
iteratorPragmas* = declPragmas + {FirstCallConv..LastCallConv, wNoSideEffect, wSideEffect, iteratorPragmas* = declPragmas + {FirstCallConv..LastCallConv, wNoSideEffect, wSideEffect,
wMagic, wBorrow, wMagic, wBorrow,
wDiscardable, wGensym, wInject, wRaises, wDiscardable, wGensym, wInject, wRaises, wEffectsOf,
wTags, wLocks, wGcSafe, wRequires, wEnsures} wTags, wLocks, wGcSafe, wRequires, wEnsures}
exprPragmas* = {wLine, wLocks, wNoRewrite, wGcSafe, wNoSideEffect} exprPragmas* = {wLine, wLocks, wNoRewrite, wGcSafe, wNoSideEffect}
stmtPragmas* = {wChecks, wObjChecks, wFieldChecks, wRangeChecks, stmtPragmas* = {wChecks, wObjChecks, wFieldChecks, wRangeChecks,
@ -59,7 +59,7 @@ const
lambdaPragmas* = {FirstCallConv..LastCallConv, lambdaPragmas* = {FirstCallConv..LastCallConv,
wNoSideEffect, wSideEffect, wNoreturn, wNosinks, wDynlib, wHeader, wNoSideEffect, wSideEffect, wNoreturn, wNosinks, wDynlib, wHeader,
wThread, wAsmNoStackFrame, wThread, wAsmNoStackFrame,
wRaises, wLocks, wTags, wRequires, wEnsures, wRaises, wLocks, wTags, wRequires, wEnsures, wEffectsOf,
wGcSafe, wCodegenDecl, wNoInit, wCompileTime} wGcSafe, wCodegenDecl, wNoInit, wCompileTime}
typePragmas* = declPragmas + {wMagic, wAcyclic, typePragmas* = declPragmas + {wMagic, wAcyclic,
wPure, wHeader, wCompilerProc, wCore, wFinal, wSize, wShallow, wPure, wHeader, wCompilerProc, wCore, wFinal, wSize, wShallow,
@ -79,7 +79,7 @@ const
paramPragmas* = {wNoalias, wInject, wGensym} paramPragmas* = {wNoalias, wInject, wGensym}
letPragmas* = varPragmas letPragmas* = varPragmas
procTypePragmas* = {FirstCallConv..LastCallConv, wVarargs, wNoSideEffect, procTypePragmas* = {FirstCallConv..LastCallConv, wVarargs, wNoSideEffect,
wThread, wRaises, wLocks, wTags, wGcSafe, wThread, wRaises, wEffectsOf, wLocks, wTags, wGcSafe,
wRequires, wEnsures} wRequires, wEnsures}
forVarPragmas* = {wInject, wGensym} forVarPragmas* = {wInject, wGensym}
allRoutinePragmas* = methodPragmas + iteratorPragmas + lambdaPragmas allRoutinePragmas* = methodPragmas + iteratorPragmas + lambdaPragmas
@ -779,6 +779,26 @@ proc semCustomPragma(c: PContext, n: PNode): PNode =
# pragma(arg) -> pragma: arg # pragma(arg) -> pragma: arg
result.transitionSonsKind(n.kind) result.transitionSonsKind(n.kind)
proc processEffectsOf(c: PContext, n: PNode; owner: PSym) =
proc processParam(c: PContext; n: PNode) =
let r = c.semExpr(c, n)
if r.kind == nkSym and r.sym.kind == skParam:
if r.sym.owner == owner:
incl r.sym.flags, sfEffectsDelayed
else:
localError(c.config, n.info, errGenerated, "parameter cannot be declared as .effectsOf")
else:
localError(c.config, n.info, errGenerated, "parameter name expected")
if n.kind notin nkPragmaCallKinds or n.len != 2:
localError(c.config, n.info, errGenerated, "parameter name expected")
else:
let it = n[1]
if it.kind in {nkCurly, nkBracket}:
for x in items(it): processParam(c, x)
else:
processParam(c, it)
proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int, proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
validPragmas: TSpecialWords, validPragmas: TSpecialWords,
comesFromPush, isStatement: bool): bool = comesFromPush, isStatement: bool): bool =
@ -895,6 +915,8 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
of wNoalias: of wNoalias:
noVal(c, it) noVal(c, it)
incl(sym.flags, sfNoalias) incl(sym.flags, sfNoalias)
of wEffectsOf:
processEffectsOf(c, it, sym)
of wThreadVar: of wThreadVar:
noVal(c, it) noVal(c, it)
incl(sym.flags, {sfThread, sfGlobal}) incl(sym.flags, {sfThread, sfGlobal})

View file

@ -400,9 +400,10 @@ when not defined(nimHasSinkInference):
include hlo, seminst, semcall include hlo, seminst, semcall
proc resetSemFlag(n: PNode) = proc resetSemFlag(n: PNode) =
excl n.flags, nfSem if n != nil:
for i in 0..<n.safeLen: excl n.flags, nfSem
resetSemFlag(n[i]) for i in 0..<n.safeLen:
resetSemFlag(n[i])
proc semAfterMacroCall(c: PContext, call, macroResult: PNode, proc semAfterMacroCall(c: PContext, call, macroResult: PNode,
s: PSym, flags: TExprFlags): PNode = s: PSym, flags: TExprFlags): PNode =

View file

@ -152,6 +152,8 @@ proc effectProblem(f, a: PType; result: var string; c: PContext) =
of efLockLevelsDiffer: of efLockLevelsDiffer:
result.add "\n The `.locks` requirements differ. Annotate the " & result.add "\n The `.locks` requirements differ. Annotate the " &
"proc with {.locks: 0.} to get extended error information." "proc with {.locks: 0.} to get extended error information."
of efEffectsDelayed:
result.add "\n The `.effectsOf` annotations differ."
when defined(drnim): when defined(drnim):
if not c.graph.compatibleProps(c.graph, f, a): if not c.graph.compatibleProps(c.graph, f, a):
result.add "\n The `.requires` or `.ensures` properties are incompatible." result.add "\n The `.requires` or `.ensures` properties are incompatible."

View file

@ -261,7 +261,9 @@ proc getGenSym*(c: PContext; s: PSym): PSym =
result = s result = s
proc considerGenSyms*(c: PContext; n: PNode) = proc considerGenSyms*(c: PContext; n: PNode) =
if n.kind == nkSym: if n == nil:
discard "can happen for nkFormalParams/nkArgList"
elif n.kind == nkSym:
let s = getGenSym(c, n.sym) let s = getGenSym(c, n.sym)
if n.sym != s: if n.sym != s:
n.sym = s n.sym = s

View file

@ -296,11 +296,12 @@ proc useVarNoInitCheck(a: PEffects; n: PNode; s: PSym) =
if {sfGlobal, sfThread} * s.flags != {} and s.kind in {skVar, skLet} and if {sfGlobal, sfThread} * s.flags != {} and s.kind in {skVar, skLet} and
s.magic != mNimvm: s.magic != mNimvm:
if s.guard != nil: guardGlobal(a, n, s.guard) if s.guard != nil: guardGlobal(a, n, s.guard)
if {sfGlobal, sfThread} * s.flags == {sfGlobal} and if strictEffects notin a.c.features:
(tfHasGCedMem in s.typ.flags or s.typ.isGCedMem): if {sfGlobal, sfThread} * s.flags == {sfGlobal} and
#if a.config.hasWarn(warnGcUnsafe): warnAboutGcUnsafe(n) (tfHasGCedMem in s.typ.flags or s.typ.isGCedMem):
markGcUnsafe(a, s) #if a.config.hasWarn(warnGcUnsafe): warnAboutGcUnsafe(n)
markSideEffect(a, s, n.info) markGcUnsafe(a, s)
markSideEffect(a, s, n.info)
if s.owner != a.owner and s.kind in {skVar, skLet, skForVar, skResult, skParam} and if s.owner != a.owner and s.kind in {skVar, skLet, skForVar, skResult, skParam} and
{sfGlobal, sfThread} * s.flags == {}: {sfGlobal, sfThread} * s.flags == {}:
a.isInnerProc = true a.isInnerProc = true
@ -474,14 +475,20 @@ proc trackTryStmt(tracked: PEffects, n: PNode) =
for id, count in items(inter): for id, count in items(inter):
if count == branches: tracked.init.add id if count == branches: tracked.init.add id
proc isIndirectCall(n: PNode, owner: PSym): bool = proc isIndirectCall(tracked: PEffects; n: PNode): bool =
# we don't count f(...) as an indirect call if 'f' is an parameter. # we don't count f(...) as an indirect call if 'f' is an parameter.
# Instead we track expressions of type tyProc too. See the manual for # Instead we track expressions of type tyProc too. See the manual for
# details: # details:
if n.kind != nkSym: if n.kind != nkSym:
result = true result = true
elif n.sym.kind == skParam: elif n.sym.kind == skParam:
result = owner != n.sym.owner or owner == nil if strictEffects in tracked.c.features:
if tracked.owner == n.sym.owner and sfEffectsDelayed in n.sym.flags:
result = false # it is not a harmful call
else:
result = true
else:
result = tracked.owner != n.sym.owner or tracked.owner == nil
elif n.sym.kind notin routineKinds: elif n.sym.kind notin routineKinds:
result = true result = true
@ -579,9 +586,14 @@ proc assumeTheWorst(tracked: PEffects; n: PNode; op: PType) =
# message(??.config, n.info, warnUser, "had to assume the worst here") # message(??.config, n.info, warnUser, "had to assume the worst here")
mergeLockLevels(tracked, n, lockLevel) mergeLockLevels(tracked, n, lockLevel)
proc isOwnedProcVar(n: PNode; owner: PSym): bool = proc isOwnedProcVar(tracked: PEffects; n: PNode): bool =
# XXX prove the soundness of this effect system rule # XXX prove the soundness of this effect system rule
result = n.kind == nkSym and n.sym.kind == skParam and owner == n.sym.owner result = n.kind == nkSym and n.sym.kind == skParam and
tracked.owner == n.sym.owner
#if result and sfPolymorphic notin n.sym.flags:
# echo tracked.config $ n.info, " different here!"
if strictEffects in tracked.c.features:
result = result and sfEffectsDelayed in n.sym.flags
proc isNoEffectList(n: PNode): bool {.inline.} = proc isNoEffectList(n: PNode): bool {.inline.} =
assert n.kind == nkEffectList assert n.kind == nkEffectList
@ -590,11 +602,15 @@ proc isNoEffectList(n: PNode): bool {.inline.} =
proc isTrival(caller: PNode): bool {.inline.} = proc isTrival(caller: PNode): bool {.inline.} =
result = caller.kind == nkSym and caller.sym.magic in {mEqProc, mIsNil, mMove, mWasMoved, mSwap} result = caller.kind == nkSym and caller.sym.magic in {mEqProc, mIsNil, mMove, mWasMoved, mSwap}
proc trackOperandForIndirectCall(tracked: PEffects, n: PNode, paramType: PType; caller: PNode) = proc trackOperandForIndirectCall(tracked: PEffects, n: PNode, formals: PType; argIndex: int; caller: PNode) =
let a = skipConvCastAndClosure(n) let a = skipConvCastAndClosure(n)
let op = a.typ let op = a.typ
let param = if formals != nil and argIndex < formals.len and formals.n != nil: formals.n[argIndex].sym else: nil
# assume indirect calls are taken here: # assume indirect calls are taken here:
if op != nil and op.kind == tyProc and n.skipConv.kind != nkNilLit and not isTrival(caller): if op != nil and op.kind == tyProc and n.skipConv.kind != nkNilLit and
not isTrival(caller) and
((param != nil and sfEffectsDelayed in param.flags) or strictEffects notin tracked.c.features):
internalAssert tracked.config, op.n[0].kind == nkEffectList internalAssert tracked.config, op.n[0].kind == nkEffectList
var effectList = op.n[0] var effectList = op.n[0]
var s = n.skipConv var s = n.skipConv
@ -607,14 +623,14 @@ proc trackOperandForIndirectCall(tracked: PEffects, n: PNode, paramType: PType;
# we have no explicit effects but it's a forward declaration and so it's # we have no explicit effects but it's a forward declaration and so it's
# stated there are no additional effects, so simply propagate them: # stated there are no additional effects, so simply propagate them:
propagateEffects(tracked, n, n.sym) propagateEffects(tracked, n, n.sym)
elif not isOwnedProcVar(a, tracked.owner): elif not isOwnedProcVar(tracked, a):
# we have no explicit effects so assume the worst: # we have no explicit effects so assume the worst:
assumeTheWorst(tracked, n, op) assumeTheWorst(tracked, n, op)
# assume GcUnsafe unless in its type; 'forward' does not matter: # assume GcUnsafe unless in its type; 'forward' does not matter:
if notGcSafe(op) and not isOwnedProcVar(a, tracked.owner): if notGcSafe(op) and not isOwnedProcVar(tracked, a):
if tracked.config.hasWarn(warnGcUnsafe): warnAboutGcUnsafe(n, tracked.config) if tracked.config.hasWarn(warnGcUnsafe): warnAboutGcUnsafe(n, tracked.config)
markGcUnsafe(tracked, a) markGcUnsafe(tracked, a)
elif tfNoSideEffect notin op.flags and not isOwnedProcVar(a, tracked.owner): elif tfNoSideEffect notin op.flags and not isOwnedProcVar(tracked, a):
markSideEffect(tracked, a, n.info) markSideEffect(tracked, a, n.info)
else: else:
mergeRaises(tracked, effectList[exceptionEffects], n) mergeRaises(tracked, effectList[exceptionEffects], n)
@ -624,6 +640,7 @@ proc trackOperandForIndirectCall(tracked: PEffects, n: PNode, paramType: PType;
markGcUnsafe(tracked, a) markGcUnsafe(tracked, a)
elif tfNoSideEffect notin op.flags: elif tfNoSideEffect notin op.flags:
markSideEffect(tracked, a, n.info) markSideEffect(tracked, a, n.info)
let paramType = if formals != nil and argIndex < formals.len: formals[argIndex] else: nil
if paramType != nil and paramType.kind in {tyVar}: if paramType != nil and paramType.kind in {tyVar}:
invalidateFacts(tracked.guards, n) invalidateFacts(tracked.guards, n)
if n.kind == nkSym and isLocalVar(tracked, n.sym): if n.kind == nkSym and isLocalVar(tracked, n.sym):
@ -724,9 +741,6 @@ proc trackBlock(tracked: PEffects, n: PNode) =
else: else:
track(tracked, n) track(tracked, n)
proc paramType(op: PType, i: int): PType =
if op != nil and i < op.len: result = op[i]
proc cstringCheck(tracked: PEffects; n: PNode) = proc cstringCheck(tracked: PEffects; n: PNode) =
if n[0].typ.kind == tyCstring and (let a = skipConv(n[1]); if n[0].typ.kind == tyCstring and (let a = skipConv(n[1]);
a.typ.kind == tyString and a.kind notin {nkStrLit..nkTripleStrLit}): a.typ.kind == tyString and a.kind notin {nkStrLit..nkTripleStrLit}):
@ -770,6 +784,25 @@ proc checkRange(c: PEffects; value: PNode; typ: PType) =
checkLe(c, lowBound, value) checkLe(c, lowBound, value)
checkLe(c, value, highBound) checkLe(c, value, highBound)
#[
proc passedToEffectsDelayedParam(tracked: PEffects; n: PNode) =
let t = n.typ.skipTypes(abstractInst)
if t.kind == tyProc:
if n.kind == nkSym and tracked.owner == n.sym.owner and sfEffectsDelayed in n.sym.flags:
discard "the arg is itself a delayed parameter, so do nothing"
else:
var effectList = t.n[0]
if effectList.len == effectListLen:
mergeRaises(tracked, effectList[exceptionEffects], n)
mergeTags(tracked, effectList[tagEffects], n)
if not importedFromC(n):
if notGcSafe(t):
if tracked.config.hasWarn(warnGcUnsafe): warnAboutGcUnsafe(n, tracked.config)
markGcUnsafe(tracked, n)
if tfNoSideEffect notin t.flags:
markSideEffect(tracked, n, n.info)
]#
proc trackCall(tracked: PEffects; n: PNode) = proc trackCall(tracked: PEffects; n: PNode) =
template gcsafeAndSideeffectCheck() = template gcsafeAndSideeffectCheck() =
if notGcSafe(op) and not importedFromC(a): if notGcSafe(op) and not importedFromC(a):
@ -811,7 +844,7 @@ proc trackCall(tracked: PEffects; n: PNode) =
elif isNoEffectList(effectList): elif isNoEffectList(effectList):
if isForwardedProc(a): if isForwardedProc(a):
propagateEffects(tracked, n, a.sym) propagateEffects(tracked, n, a.sym)
elif isIndirectCall(a, tracked.owner): elif isIndirectCall(tracked, a):
assumeTheWorst(tracked, n, op) assumeTheWorst(tracked, n, op)
gcsafeAndSideeffectCheck() gcsafeAndSideeffectCheck()
else: else:
@ -819,7 +852,8 @@ proc trackCall(tracked: PEffects; n: PNode) =
mergeTags(tracked, effectList[tagEffects], n) mergeTags(tracked, effectList[tagEffects], n)
gcsafeAndSideeffectCheck() gcsafeAndSideeffectCheck()
if a.kind != nkSym or a.sym.magic != mNBindSym: if a.kind != nkSym or a.sym.magic != mNBindSym:
for i in 1..<n.len: trackOperandForIndirectCall(tracked, n[i], paramType(op, i), a) for i in 1..<n.len:
trackOperandForIndirectCall(tracked, n[i], op, i, a)
if a.kind == nkSym and a.sym.magic in {mNew, mNewFinalize, mNewSeq}: if a.kind == nkSym and a.sym.magic in {mNew, mNewFinalize, mNewSeq}:
# may not look like an assignment, but it is: # may not look like an assignment, but it is:
let arg = n[1] let arg = n[1]
@ -1225,7 +1259,7 @@ proc subtypeRelation(g: ModuleGraph; spec, real: PNode): bool =
else: else:
return safeInheritanceDiff(g.excType(real), spec.typ) <= 0 return safeInheritanceDiff(g.excType(real), spec.typ) <= 0
proc checkRaisesSpec(g: ModuleGraph; spec, real: PNode, msg: string, hints: bool; proc checkRaisesSpec(g: ModuleGraph; emitWarnings: bool; spec, real: PNode, msg: string, hints: bool;
effectPredicate: proc (g: ModuleGraph; a, b: PNode): bool {.nimcall.}; effectPredicate: proc (g: ModuleGraph; a, b: PNode): bool {.nimcall.};
hintsArg: PNode = nil) = hintsArg: PNode = nil) =
# check that any real exception is listed in 'spec'; mark those as used; # check that any real exception is listed in 'spec'; mark those as used;
@ -1241,7 +1275,8 @@ proc checkRaisesSpec(g: ModuleGraph; spec, real: PNode, msg: string, hints: bool
pushInfoContext(g.config, spec.info) pushInfoContext(g.config, spec.info)
var rr = if r.kind == nkRaiseStmt: r[0] else: r var rr = if r.kind == nkRaiseStmt: r[0] else: r
while rr.kind in {nkStmtList, nkStmtListExpr} and rr.len > 0: rr = rr.lastSon while rr.kind in {nkStmtList, nkStmtListExpr} and rr.len > 0: rr = rr.lastSon
localError(g.config, r.info, errGenerated, renderTree(rr) & " " & msg & typeToString(r.typ)) message(g.config, r.info, if emitWarnings: warnEffect else: errGenerated,
renderTree(rr) & " " & msg & typeToString(r.typ))
popInfoContext(g.config) popInfoContext(g.config)
# hint about unnecessarily listed exception types: # hint about unnecessarily listed exception types:
if hints: if hints:
@ -1258,11 +1293,11 @@ proc checkMethodEffects*(g: ModuleGraph; disp, branch: PSym) =
let p = disp.ast[pragmasPos] let p = disp.ast[pragmasPos]
let raisesSpec = effectSpec(p, wRaises) let raisesSpec = effectSpec(p, wRaises)
if not isNil(raisesSpec): if not isNil(raisesSpec):
checkRaisesSpec(g, raisesSpec, actual[exceptionEffects], checkRaisesSpec(g, false, raisesSpec, actual[exceptionEffects],
"can raise an unlisted exception: ", hints=off, subtypeRelation) "can raise an unlisted exception: ", hints=off, subtypeRelation)
let tagsSpec = effectSpec(p, wTags) let tagsSpec = effectSpec(p, wTags)
if not isNil(tagsSpec): if not isNil(tagsSpec):
checkRaisesSpec(g, tagsSpec, actual[tagEffects], checkRaisesSpec(g, false, tagsSpec, actual[tagEffects],
"can have an unlisted effect: ", hints=off, subtypeRelation) "can have an unlisted effect: ", hints=off, subtypeRelation)
if sfThread in disp.flags and notGcSafe(branch.typ): if sfThread in disp.flags and notGcSafe(branch.typ):
localError(g.config, branch.info, "base method is GC-safe, but '$1' is not" % localError(g.config, branch.info, "base method is GC-safe, but '$1' is not" %
@ -1283,7 +1318,7 @@ proc checkMethodEffects*(g: ModuleGraph; disp, branch: PSym) =
"base method has lock level $1, but dispatcher has $2" % "base method has lock level $1, but dispatcher has $2" %
[$branch.typ.lockLevel, $disp.typ.lockLevel]) [$branch.typ.lockLevel, $disp.typ.lockLevel])
proc setEffectsForProcType*(g: ModuleGraph; t: PType, n: PNode) = proc setEffectsForProcType*(g: ModuleGraph; t: PType, n: PNode; s: PSym = nil) =
var effects = t.n[0] var effects = t.n[0]
if t.kind != tyProc or effects.kind != nkEffectList: return if t.kind != tyProc or effects.kind != nkEffectList: return
if n.kind != nkEmpty: if n.kind != nkEmpty:
@ -1292,9 +1327,14 @@ proc setEffectsForProcType*(g: ModuleGraph; t: PType, n: PNode) =
let raisesSpec = effectSpec(n, wRaises) let raisesSpec = effectSpec(n, wRaises)
if not isNil(raisesSpec): if not isNil(raisesSpec):
effects[exceptionEffects] = raisesSpec effects[exceptionEffects] = raisesSpec
elif s != nil and (s.magic != mNone or {sfImportc, sfExportc} * s.flags == {sfImportc}):
effects[exceptionEffects] = newNodeI(nkArgList, effects.info)
let tagsSpec = effectSpec(n, wTags) let tagsSpec = effectSpec(n, wTags)
if not isNil(tagsSpec): if not isNil(tagsSpec):
effects[tagEffects] = tagsSpec effects[tagEffects] = tagsSpec
elif s != nil and (s.magic != mNone or {sfImportc, sfExportc} * s.flags == {sfImportc}):
effects[tagEffects] = newNodeI(nkArgList, effects.info)
let requiresSpec = propSpec(n, wRequires) let requiresSpec = propSpec(n, wRequires)
if not isNil(requiresSpec): if not isNil(requiresSpec):
@ -1304,15 +1344,21 @@ proc setEffectsForProcType*(g: ModuleGraph; t: PType, n: PNode) =
effects[ensuresEffects] = ensuresSpec effects[ensuresEffects] = ensuresSpec
effects[pragmasEffects] = n effects[pragmasEffects] = n
if s != nil and s.magic != mNone:
if s.magic != mEcho:
t.flags.incl tfNoSideEffect
proc initEffects(g: ModuleGraph; effects: PNode; s: PSym; t: var TEffects; c: PContext) = proc rawInitEffects(g: ModuleGraph; effects: PNode) =
newSeq(effects.sons, effectListLen) newSeq(effects.sons, effectListLen)
effects[exceptionEffects] = newNodeI(nkArgList, s.info) effects[exceptionEffects] = newNodeI(nkArgList, effects.info)
effects[tagEffects] = newNodeI(nkArgList, s.info) effects[tagEffects] = newNodeI(nkArgList, effects.info)
effects[requiresEffects] = g.emptyNode effects[requiresEffects] = g.emptyNode
effects[ensuresEffects] = g.emptyNode effects[ensuresEffects] = g.emptyNode
effects[pragmasEffects] = g.emptyNode effects[pragmasEffects] = g.emptyNode
proc initEffects(g: ModuleGraph; effects: PNode; s: PSym; t: var TEffects; c: PContext) =
rawInitEffects(g, effects)
t.exc = effects[exceptionEffects] t.exc = effects[exceptionEffects]
t.tags = effects[tagEffects] t.tags = effects[tagEffects]
t.owner = s t.owner = s
@ -1341,10 +1387,14 @@ proc trackProc*(c: PContext; s: PSym, body: PNode) =
if effects.kind != nkEffectList: return if effects.kind != nkEffectList: return
# effects already computed? # effects already computed?
if not s.hasRealBody: return if not s.hasRealBody: return
if effects.len == effectListLen: return let emitWarnings = tfEffectSystemWorkaround in s.typ.flags
if effects.len == effectListLen and not emitWarnings: return
var inferredEffects = newNodeI(nkEffectList, s.info)
var t: TEffects var t: TEffects
initEffects(g, effects, s, t, c) initEffects(g, inferredEffects, s, t, c)
rawInitEffects g, effects
track(t, body) track(t, body)
if s.kind != skMacro: if s.kind != skMacro:
@ -1369,17 +1419,21 @@ proc trackProc*(c: PContext; s: PSym, body: PNode) =
let p = s.ast[pragmasPos] let p = s.ast[pragmasPos]
let raisesSpec = effectSpec(p, wRaises) let raisesSpec = effectSpec(p, wRaises)
if not isNil(raisesSpec): if not isNil(raisesSpec):
checkRaisesSpec(g, raisesSpec, t.exc, "can raise an unlisted exception: ", checkRaisesSpec(g, emitWarnings, raisesSpec, t.exc, "can raise an unlisted exception: ",
hints=on, subtypeRelation, hintsArg=s.ast[0]) hints=on, subtypeRelation, hintsArg=s.ast[0])
# after the check, use the formal spec: # after the check, use the formal spec:
effects[exceptionEffects] = raisesSpec effects[exceptionEffects] = raisesSpec
else:
effects[exceptionEffects] = t.exc
let tagsSpec = effectSpec(p, wTags) let tagsSpec = effectSpec(p, wTags)
if not isNil(tagsSpec): if not isNil(tagsSpec):
checkRaisesSpec(g, tagsSpec, t.tags, "can have an unlisted effect: ", checkRaisesSpec(g, emitWarnings, tagsSpec, t.tags, "can have an unlisted effect: ",
hints=off, subtypeRelation) hints=off, subtypeRelation)
# after the check, use the formal spec: # after the check, use the formal spec:
effects[tagEffects] = tagsSpec effects[tagEffects] = tagsSpec
else:
effects[tagEffects] = t.tags
let requiresSpec = propSpec(p, wRequires) let requiresSpec = propSpec(p, wRequires)
if not isNil(requiresSpec): if not isNil(requiresSpec):

View file

@ -1953,6 +1953,10 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
if not hasProto: if not hasProto:
implicitPragmas(c, s, n.info, validPragmas) implicitPragmas(c, s, n.info, validPragmas)
if n[pragmasPos].kind != nkEmpty:
setEffectsForProcType(c.graph, s.typ, n[pragmasPos], s)
s.typ.flags.incl tfEffectSystemWorkaround
# To ease macro generation that produce forwarded .async procs we now # To ease macro generation that produce forwarded .async procs we now
# allow a bit redundancy in the pragma declarations. The rule is # allow a bit redundancy in the pragma declarations. The rule is
# a prototype's pragma list must be a superset of the current pragma # a prototype's pragma list must be a superset of the current pragma
@ -2211,8 +2215,9 @@ proc evalInclude(c: PContext, n: PNode): PNode =
incMod(c, n, it, result) incMod(c, n, it, result)
proc setLine(n: PNode, info: TLineInfo) = proc setLine(n: PNode, info: TLineInfo) =
for i in 0..<n.safeLen: setLine(n[i], info) if n != nil:
n.info = info for i in 0..<n.safeLen: setLine(n[i], info)
n.info = info
proc semPragmaBlock(c: PContext, n: PNode): PNode = proc semPragmaBlock(c: PContext, n: PNode): PNode =
checkSonsLen(n, 2, c.config) checkSonsLen(n, 2, c.config)

View file

@ -1364,10 +1364,14 @@ type
efTagsDiffer efTagsDiffer
efTagsUnknown efTagsUnknown
efLockLevelsDiffer efLockLevelsDiffer
efEffectsDelayed
proc compatibleEffects*(formal, actual: PType): EffectsCompat = proc compatibleEffects*(formal, actual: PType): EffectsCompat =
# for proc type compatibility checking: # for proc type compatibility checking:
assert formal.kind == tyProc and actual.kind == tyProc assert formal.kind == tyProc and actual.kind == tyProc
#if tfEffectSystemWorkaround in actual.flags:
# return efCompat
if formal.n[0].kind != nkEffectList or if formal.n[0].kind != nkEffectList or
actual.n[0].kind != nkEffectList: actual.n[0].kind != nkEffectList:
return efTagsUnknown return efTagsUnknown
@ -1391,9 +1395,17 @@ proc compatibleEffects*(formal, actual: PType): EffectsCompat =
# spec requires some exception or tag, but we don't know anything: # spec requires some exception or tag, but we don't know anything:
if real.len == 0: return efTagsUnknown if real.len == 0: return efTagsUnknown
let res = compatibleEffectsAux(st, real[tagEffects]) let res = compatibleEffectsAux(st, real[tagEffects])
if not res: return efTagsDiffer if not res:
#if tfEffectSystemWorkaround notin actual.flags:
return efTagsDiffer
if formal.lockLevel.ord < 0 or if formal.lockLevel.ord < 0 or
actual.lockLevel.ord <= formal.lockLevel.ord: actual.lockLevel.ord <= formal.lockLevel.ord:
for i in 1 ..< min(formal.n.len, actual.n.len):
if formal.n[i].sym.flags * {sfEffectsDelayed} != actual.n[i].sym.flags * {sfEffectsDelayed}:
result = efEffectsDelayed
break
result = efCompat result = efCompat
else: else:
result = efLockLevelsDiffer result = efLockLevelsDiffer
@ -1597,7 +1609,8 @@ proc typeMismatch*(conf: ConfigRef; info: TLineInfo, formal, actual: PType, n: P
msg.add "\n.tag effect is 'any tag allowed'" msg.add "\n.tag effect is 'any tag allowed'"
of efLockLevelsDiffer: of efLockLevelsDiffer:
msg.add "\nlock levels differ" msg.add "\nlock levels differ"
of efEffectsDelayed:
msg.add "\n.effectsOf annotations differ"
localError(conf, info, msg) localError(conf, info, msg)
proc isTupleRecursive(t: PType, cycleDetector: var IntSet): bool = proc isTupleRecursive(t: PType, cycleDetector: var IntSet): bool =

View file

@ -35,7 +35,7 @@ type
wMagic = "magic", wThread = "thread", wFinal = "final", wProfiler = "profiler", wMagic = "magic", wThread = "thread", wFinal = "final", wProfiler = "profiler",
wMemTracker = "memtracker", wObjChecks = "objchecks", wMemTracker = "memtracker", wObjChecks = "objchecks",
wIntDefine = "intdefine", wStrDefine = "strdefine", wBoolDefine = "booldefine", wIntDefine = "intdefine", wStrDefine = "strdefine", wBoolDefine = "booldefine",
wCursor = "cursor", wNoalias = "noalias", wCursor = "cursor", wNoalias = "noalias", wEffectsOf = "effectsOf",
wImmediate = "immediate", wConstructor = "constructor", wDestructor = "destructor", wImmediate = "immediate", wConstructor = "constructor", wDestructor = "destructor",
wDelegator = "delegator", wOverride = "override", wImportCpp = "importcpp", wDelegator = "delegator", wOverride = "override", wImportCpp = "importcpp",

View file

@ -4721,6 +4721,11 @@ and rely on functionality of the `x` object to get exception details.
Effect system Effect system
============= =============
**Note**: The rules for effect tracking changed with the release of version
1.6 of the Nim compiler. This section describes the new rules that are activated
via `--experimental:strictEffects`.
Exception tracking Exception tracking
------------------ ------------------
@ -4770,35 +4775,24 @@ possibly raised exceptions; the algorithm operates on `p`'s call graph:
1. Every indirect call via some proc type `T` is assumed to 1. Every indirect call via some proc type `T` is assumed to
raise `system.Exception` (the base type of the exception hierarchy) and raise `system.Exception` (the base type of the exception hierarchy) and
thus any exception unless `T` has an explicit `raises` list. thus any exception unless `T` has an explicit `raises` list.
However, if the call is of the form `f(...)` where `f` is a parameter of the currently analyzed routine it is ignored. The call is optimistically assumed to have no effect. Rule 2 compensates for this case. However, if the call is of the form `f(...)` where `f` is a parameter of
2. Every expression of some proc type within a call that is not a call the currently analyzed routine it is ignored that is marked as `.effectsOf: f`.
itself (and not nil) is assumed to be called indirectly somehow and thus The call is optimistically assumed to have no effect.
Rule 2 compensates for this case.
2. Every expression `e` of some proc type within a call that is passed to parameter
marked as `.effectsOf` is assumed to be called indirectly and thus
its raises list is added to `p`'s raises list. its raises list is added to `p`'s raises list.
3. Every call to a proc `q` which has an unknown body (due to a forward 3. Every call to a proc `q` which has an unknown body (due to a forward
declaration or an `importc` pragma) is assumed to declaration) is assumed to
raise `system.Exception` unless `q` has an explicit `raises` list. raise `system.Exception` unless `q` has an explicit `raises` list.
Procs that are `importc`'ed are assumed to have `.raises: []`, unless explicitly
declared otherwise.
4. Every call to a method `m` is assumed to 4. Every call to a method `m` is assumed to
raise `system.Exception` unless `m` has an explicit `raises` list. raise `system.Exception` unless `m` has an explicit `raises` list.
5. For every other call, the analysis can determine an exact `raises` list. 5. For every other call, the analysis can determine an exact `raises` list.
6. For determining a `raises` list, the `raise` and `try` statements 6. For determining a `raises` list, the `raise` and `try` statements
of `p` are taken into consideration. of `p` are taken into consideration.
Rules 1-2 ensure the following works:
.. code-block:: nim
proc noRaise(x: proc()) {.raises: [].} =
# unknown call that might raise anything, but valid:
x()
proc doRaise() {.raises: [IOError].} =
raise newException(IOError, "IO")
proc use() {.raises: [].} =
# doesn't compile! Can raise IOError!
noRaise(doRaise)
So in many cases a callback does not cause the compiler to be overly
conservative in its effect analysis.
Exceptions inheriting from `system.Defect` are not tracked with Exceptions inheriting from `system.Defect` are not tracked with
the `.raises: []` exception tracking mechanism. This is more consistent with the the `.raises: []` exception tracking mechanism. This is more consistent with the
@ -4823,6 +4817,60 @@ with `--panics:on`:option: Defects become unrecoverable errors.
(Since version 1.4 of the language.) (Since version 1.4 of the language.)
EffectsOf annotation
--------------------
Rules 1-2 of the exception tracking inference rules (see the previous section)
ensure the following works:
.. code-block:: nim
proc weDontRaiseButMaybeTheCallback(callback: proc()) {.raises: [], effectsOf: callback.} =
callback()
proc doRaise() {.raises: [IOError].} =
raise newException(IOError, "IO")
proc use() {.raises: [].} =
# doesn't compile! Can raise IOError!
weDontRaiseButMaybeTheCallback(doRaise)
As can be seen from the example, a parameter of type `proc (...)` can be
annotated as `.effectsOf`. Such a parameter allows for effect polymorphism:
The proc `weDontRaiseButMaybeTheCallback` raises the exceptions
that `callback` raises.
So in many cases a callback does not cause the compiler to be overly
conservative in its effect analysis:
.. code-block:: nim
:test: "nim c $1"
:status: 1
{.push warningAsError[Effect]: on.}
{.experimental: "strictEffects".}
import algorithm
type
MyInt = distinct int
var toSort = @[MyInt 1, MyInt 2, MyInt 3]
proc cmpN(a, b: MyInt): int =
cmp(a.int, b.int)
proc harmless {.raises: [].} =
toSort.sort cmpN
proc cmpE(a, b: MyInt): int {.raises: [Exception].} =
cmp(a.int, b.int)
proc harmfull {.raises: [].} =
# does not compile, `sort` can now raise Exception
toSort.sort cmpE
Tag tracking Tag tracking
------------ ------------
@ -4831,7 +4879,7 @@ is an *effect*. Other effects can also be defined. A user defined effect is a
means to *tag* a routine and to perform checks against this tag: means to *tag* a routine and to perform checks against this tag:
.. code-block:: nim .. code-block:: nim
:test: "nim c $1" :test: "nim c --warningAsError:Effect:on $1"
:status: 1 :status: 1
type IO = object ## input/output effect type IO = object ## input/output effect
@ -4848,6 +4896,78 @@ The inference for tag tracking is analogous to the inference for
exception tracking. exception tracking.
Side effects
------------
The `noSideEffect` pragma is used to mark a proc/iterator that can have only
side effects through parameters. This means that the proc/iterator only changes locations that are
reachable from its parameters and the return value only depends on the
parameters. If none of its parameters have the type `var`, `ref`, `ptr`, `cstring`, or `proc`,
then no locations are modified.
In other words, a routine has no side effects if it does not access a threadlocal
or global variable and it does not call any routine that has a side effect.
It is a static error to mark a proc/iterator to have no side effect if the compiler cannot verify this.
As a special semantic rule, the built-in `debugEcho
<system.html#debugEcho,varargs[typed,]>`_ pretends to be free of side effects
so that it can be used for debugging routines marked as `noSideEffect`.
`func` is syntactic sugar for a proc with no side effects:
.. code-block:: nim
func `+` (x, y: int): int
To override the compiler's side effect analysis a `{.noSideEffect.}`
`cast` pragma block can be used:
.. code-block:: nim
func f() =
{.cast(noSideEffect).}:
echo "test"
**Side effects are usually inferred. The inference for side effects is
analogous to the inference for exception tracking.**
GC safety effect
----------------
We call a proc `p` `GC safe`:idx: when it doesn't access any global variable
that contains GC'ed memory (`string`, `seq`, `ref` or a closure) either
directly or indirectly through a call to a GC unsafe proc.
**The GC safety property is usually inferred. The inference for GC safety is
analogous to the inference for exception tracking.**
The `gcsafe`:idx: annotation can be used to mark a proc to be gcsafe,
otherwise this property is inferred by the compiler. Note that `noSideEffect`
implies `gcsafe`.
Routines that are imported from C are always assumed to be `gcsafe`.
To override the compiler's gcsafety analysis a `{.cast(gcsafe).}` pragma block can
be used:
.. code-block:: nim
var
someGlobal: string = "some string here"
perThread {.threadvar.}: string
proc setPerThread() =
{.cast(gcsafe).}:
deepCopy(perThread, someGlobal)
See also:
- `Shared heap memory management <gc.html>`_.
Effects pragma Effects pragma
-------------- --------------
@ -6366,55 +6486,6 @@ This pragma can also take in an optional warning string to relay to developers.
proc thing(x: bool) {.deprecated: "use thong instead".} proc thing(x: bool) {.deprecated: "use thong instead".}
noSideEffect pragma
-------------------
The `noSideEffect` pragma is used to mark a proc/iterator that can have only
side effects through parameters. This means that the proc/iterator only changes locations that are
reachable from its parameters and the return value only depends on the
parameters. If none of its parameters have the type `var`, `ref`, `ptr`, `cstring`, or `proc`,
then no locations are modified.
It is a static error to mark a proc/iterator to have no side effect if the compiler cannot verify this.
As a special semantic rule, the built-in `debugEcho
<system.html#debugEcho,varargs[typed,]>`_ pretends to be free of side effects
so that it can be used for debugging routines marked as `noSideEffect`.
`func` is syntactic sugar for a proc with no side effects:
.. code-block:: nim
func `+` (x, y: int): int
To override the compiler's side effect analysis a `{.noSideEffect.}`
`cast` pragma block can be used:
.. code-block:: nim
func f() =
{.cast(noSideEffect).}:
echo "test"
When a `noSideEffect` proc has proc params `bar`, whether it can be used inside a `noSideEffect` context
depends on what the compiler knows about `bar`:
.. code-block:: nim
:test: "nim c $1"
func foo(bar: proc(): int): int = bar()
var count = 0
proc fn1(): int = 1
proc fn2(): int = (count.inc; count)
func fun1() = discard foo(fn1) # ok because fn1 is inferred as `func`
# func fun2() = discard foo(fn2) # would give: Error: 'fun2' can have side effects
# with callbacks, the compiler is conservative, ie that bar will have side effects
var foo2: type(foo) = foo
func main() =
discard foo(fn1) # ok
# discard foo2(fn1) # now this errors
compileTime pragma compileTime pragma
------------------ ------------------
@ -7861,6 +7932,10 @@ collected) heap, and sharing of memory is restricted to global variables. This
helps to prevent race conditions. GC efficiency is improved quite a lot, helps to prevent race conditions. GC efficiency is improved quite a lot,
because the GC never has to stop other threads and see what they reference. because the GC never has to stop other threads and see what they reference.
The only way to create a thread is via `spawn` or
`createThread`. The invoked proc must not use `var` parameters nor must
any of its parameters contain a `ref` or `closure` type. This enforces
the *no heap sharing restriction*.
Thread pragma Thread pragma
------------- -------------
@ -7875,43 +7950,6 @@ A thread proc is passed to `createThread` or `spawn` and invoked
indirectly; so the `thread` pragma implies `procvar`. indirectly; so the `thread` pragma implies `procvar`.
GC safety
---------
We call a proc `p` `GC safe`:idx: when it doesn't access any global variable
that contains GC'ed memory (`string`, `seq`, `ref` or a closure) either
directly or indirectly through a call to a GC unsafe proc.
The `gcsafe`:idx: annotation can be used to mark a proc to be gcsafe,
otherwise this property is inferred by the compiler. Note that `noSideEffect`
implies `gcsafe`. The only way to create a thread is via `spawn` or
`createThread`. The invoked proc must not use `var` parameters nor must
any of its parameters contain a `ref` or `closure` type. This enforces
the *no heap sharing restriction*.
Routines that are imported from C are always assumed to be `gcsafe`.
To disable the GC-safety checking the `--threadAnalysis:off`:option: command-line
switch can be used. This is a temporary workaround to ease the porting effort
from old code to the new threading model.
To override the compiler's gcsafety analysis a `{.cast(gcsafe).}` pragma block can
be used:
.. code-block:: nim
var
someGlobal: string = "some string here"
perThread {.threadvar.}: string
proc setPerThread() =
{.cast(gcsafe).}:
deepCopy(perThread, someGlobal)
See also:
- `Shared heap memory management <gc.html>`_.
Threadvar pragma Threadvar pragma
---------------- ----------------

View file

@ -147,8 +147,13 @@ proc reversed*[T](a: openArray[T], first: Natural, last: int): seq[T]
{.inline, deprecated: "use: `reversed(toOpenArray(a, first, last))`".} = {.inline, deprecated: "use: `reversed(toOpenArray(a, first, last))`".} =
reversed(toOpenArray(a, first, last)) reversed(toOpenArray(a, first, last))
when defined(nimHasEffectsOf):
{.experimental: "strictEffects".}
else:
{.pragma: effectsOf.}
proc binarySearch*[T, K](a: openArray[T], key: K, proc binarySearch*[T, K](a: openArray[T], key: K,
cmp: proc (x: T, y: K): int {.closure.}): int = cmp: proc (x: T, y: K): int {.closure.}): int {.effectsOf: cmp.} =
## Binary search for `key` in `a`. Return the index of `key` or -1 if not found. ## Binary search for `key` in `a`. Return the index of `key` or -1 if not found.
## Assumes that `a` is sorted according to `cmp`. ## Assumes that `a` is sorted according to `cmp`.
## ##
@ -210,7 +215,7 @@ const
onlySafeCode = true onlySafeCode = true
proc lowerBound*[T, K](a: openArray[T], key: K, proc lowerBound*[T, K](a: openArray[T], key: K,
cmp: proc(x: T, k: K): int {.closure.}): int = cmp: proc(x: T, k: K): int {.closure.}): int {.effectsOf: cmp.} =
## Returns the index of the first element in `a` that is not less than ## Returns the index of the first element in `a` that is not less than
## (i.e. greater or equal to) `key`, or last if no such element is found. ## (i.e. greater or equal to) `key`, or last if no such element is found.
## In other words if you have a sorted sequence and you call ## In other words if you have a sorted sequence and you call
@ -260,7 +265,7 @@ proc lowerBound*[T](a: openArray[T], key: T): int = lowerBound(a, key, cmp[T])
## * `upperBound proc<#upperBound,openArray[T],T>`_ ## * `upperBound proc<#upperBound,openArray[T],T>`_
proc upperBound*[T, K](a: openArray[T], key: K, proc upperBound*[T, K](a: openArray[T], key: K,
cmp: proc(x: T, k: K): int {.closure.}): int = cmp: proc(x: T, k: K): int {.closure.}): int {.effectsOf: cmp.} =
## Returns the index of the first element in `a` that is greater than ## Returns the index of the first element in `a` that is greater than
## `key`, or last if no such element is found. ## `key`, or last if no such element is found.
## In other words if you have a sorted sequence and you call ## In other words if you have a sorted sequence and you call
@ -318,7 +323,7 @@ template `<-`(a, b) =
copyMem(addr(a), addr(b), sizeof(T)) copyMem(addr(a), addr(b), sizeof(T))
proc mergeAlt[T](a, b: var openArray[T], lo, m, hi: int, proc mergeAlt[T](a, b: var openArray[T], lo, m, hi: int,
cmp: proc (x, y: T): int {.closure.}, order: SortOrder) = cmp: proc (x, y: T): int {.closure.}, order: SortOrder) {.effectsOf: cmp.} =
# Optimization: If max(left) <= min(right) there is nothing to do! # Optimization: If max(left) <= min(right) there is nothing to do!
# 1 2 3 4 ## 5 6 7 8 # 1 2 3 4 ## 5 6 7 8
# -> O(n) for sorted arrays. # -> O(n) for sorted arrays.
@ -358,7 +363,7 @@ proc mergeAlt[T](a, b: var openArray[T], lo, m, hi: int,
func sort*[T](a: var openArray[T], func sort*[T](a: var openArray[T],
cmp: proc (x, y: T): int {.closure.}, cmp: proc (x, y: T): int {.closure.},
order = SortOrder.Ascending) = order = SortOrder.Ascending) {.effectsOf: cmp.} =
## Default Nim sort (an implementation of merge sort). The sorting ## Default Nim sort (an implementation of merge sort). The sorting
## is guaranteed to be stable (that is, equal elements stay in the same order) ## is guaranteed to be stable (that is, equal elements stay in the same order)
## and the worst case is guaranteed to be O(n log n). ## and the worst case is guaranteed to be O(n log n).
@ -420,7 +425,7 @@ proc sort*[T](a: var openArray[T], order = SortOrder.Ascending) = sort[T](a,
## * `sortedByIt template<#sortedByIt.t,untyped,untyped>`_ ## * `sortedByIt template<#sortedByIt.t,untyped,untyped>`_
proc sorted*[T](a: openArray[T], cmp: proc(x, y: T): int {.closure.}, proc sorted*[T](a: openArray[T], cmp: proc(x, y: T): int {.closure.},
order = SortOrder.Ascending): seq[T] = order = SortOrder.Ascending): seq[T] {.effectsOf: cmp.} =
## Returns `a` sorted by `cmp` in the specified `order`. ## Returns `a` sorted by `cmp` in the specified `order`.
## ##
## **See also:** ## **See also:**
@ -497,7 +502,7 @@ template sortedByIt*(seq1, op: untyped): untyped =
func isSorted*[T](a: openArray[T], func isSorted*[T](a: openArray[T],
cmp: proc(x, y: T): int {.closure.}, cmp: proc(x, y: T): int {.closure.},
order = SortOrder.Ascending): bool = order = SortOrder.Ascending): bool {.effectsOf: cmp.} =
## Checks to see whether `a` is already sorted in `order` ## Checks to see whether `a` is already sorted in `order`
## using `cmp` for the comparison. The parameters are identical ## using `cmp` for the comparison. The parameters are identical
## to `sort`. Requires O(n) time. ## to `sort`. Requires O(n) time.
@ -545,7 +550,7 @@ proc isSorted*[T](a: openArray[T], order = SortOrder.Ascending): bool =
proc merge*[T]( proc merge*[T](
result: var seq[T], result: var seq[T],
x, y: openArray[T], cmp: proc(x, y: T): int {.closure.} x, y: openArray[T], cmp: proc(x, y: T): int {.closure.}
) {.since: (1, 5, 1).} = ) {.since: (1, 5, 1), effectsOf: cmp.} =
## Merges two sorted `openArray`. `x` and `y` are assumed to be sorted. ## Merges two sorted `openArray`. `x` and `y` are assumed to be sorted.
## If you do not wish to provide your own `cmp`, ## If you do not wish to provide your own `cmp`,
## you may use `system.cmp` or instead call the overloaded ## you may use `system.cmp` or instead call the overloaded

View file

@ -8,13 +8,18 @@ import os
when defined(windows): when defined(windows):
from strutils import replace from strutils import replace
when defined(nimHasEffectsOf):
{.experimental: "strictEffects".}
else:
{.pragma: effectsOf.}
type type
PathEntry* = object PathEntry* = object
kind*: PathComponent kind*: PathComponent
path*: string path*: string
iterator walkDirRecFilter*(dir: string, follow: proc(entry: PathEntry): bool = nil, iterator walkDirRecFilter*(dir: string, follow: proc(entry: PathEntry): bool = nil,
relative = false, checkDir = true): PathEntry {.tags: [ReadDirEffect].} = relative = false, checkDir = true): PathEntry {.tags: [ReadDirEffect], effectsOf: follow.} =
## Improved `os.walkDirRec`. ## Improved `os.walkDirRec`.
#[ #[
note: a yieldFilter isn't needed because caller can filter at call site, without note: a yieldFilter isn't needed because caller can filter at call site, without

View file

@ -78,7 +78,7 @@ proc c_fputs(c: cstring, f: File): cint {.
proc c_fgets(c: cstring, n: cint, f: File): cstring {. proc c_fgets(c: cstring, n: cint, f: File): cstring {.
importc: "fgets", header: "<stdio.h>", tags: [ReadIOEffect].} importc: "fgets", header: "<stdio.h>", tags: [ReadIOEffect].}
proc c_fgetc(stream: File): cint {. proc c_fgetc(stream: File): cint {.
importc: "fgetc", header: "<stdio.h>", tags: [ReadIOEffect].} importc: "fgetc", header: "<stdio.h>", tags: [].}
proc c_ungetc(c: cint, f: File): cint {. proc c_ungetc(c: cint, f: File): cint {.
importc: "ungetc", header: "<stdio.h>", tags: [].} importc: "ungetc", header: "<stdio.h>", tags: [].}
proc c_putc(c: cint, stream: File): cint {. proc c_putc(c: cint, stream: File): cint {.

View file

@ -261,7 +261,7 @@ proc cpDir*(`from`, to: string) {.raises: [OSError].} =
checkOsError() checkOsError()
proc exec*(command: string) {. proc exec*(command: string) {.
raises: [OSError], tags: [ExecIOEffect].} = raises: [OSError], tags: [ExecIOEffect, WriteIOEffect].} =
## Executes an external process. If the external process terminates with ## Executes an external process. If the external process terminates with
## a non-zero exit code, an OSError exception is raised. ## a non-zero exit code, an OSError exception is raised.
## ##
@ -274,7 +274,7 @@ proc exec*(command: string) {.
checkOsError() checkOsError()
proc exec*(command: string, input: string, cache = "") {. proc exec*(command: string, input: string, cache = "") {.
raises: [OSError], tags: [ExecIOEffect].} = raises: [OSError], tags: [ExecIOEffect, WriteIOEffect].} =
## Executes an external process. If the external process terminates with ## Executes an external process. If the external process terminates with
## a non-zero exit code, an OSError exception is raised. ## a non-zero exit code, an OSError exception is raised.
log "exec: " & command: log "exec: " & command:
@ -284,7 +284,7 @@ proc exec*(command: string, input: string, cache = "") {.
echo output echo output
proc selfExec*(command: string) {. proc selfExec*(command: string) {.
raises: [OSError], tags: [ExecIOEffect].} = raises: [OSError], tags: [ExecIOEffect, WriteIOEffect].} =
## Executes an external command with the current nim/nimble executable. ## Executes an external command with the current nim/nimble executable.
## `Command` must not contain the "nim " part. ## `Command` must not contain the "nim " part.
let c = selfExe() & " " & command let c = selfExe() & " " & command

View file

@ -122,7 +122,7 @@ window.addEventListener('DOMContentLoaded', main);
<h1><a class="toc-backref" href="#12">Procs</a></h1> <h1><a class="toc-backref" href="#12">Procs</a></h1>
<dl class="item"> <dl class="item">
<div id="foo"> <div id="foo">
<dt><pre><span class="Keyword">proc</span> <a href="#foo"><span class="Identifier">foo</span></a><span class="Other">(</span><span class="Other">)</span> {.<span><span class="Other pragmadots">...</span></span><span class="pragmawrap"><span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Other">]</span><span class="Other">,</span> <span class="Identifier">tags</span><span class="Other">:</span> <span class="Other">[</span><span class="Other">]</span></span>.}</pre></dt> <dt><pre><span class="Keyword">proc</span> <a href="#foo"><span class="Identifier">foo</span></a><span class="Other">(</span><span class="Other">)</span> {.<span><span class="Other pragmadots">...</span></span><span class="pragmawrap"><span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Other">]</span><span class="Other">,</span> <span class="Identifier">tags</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">WriteIOEffect</span><span class="Other">]</span></span>.}</pre></dt>
<dd> <dd>
I do foo I do foo

View file

@ -654,7 +654,7 @@ This is deprecated with a message.
</div> </div>
<div id="c_nonexistent,cstring"> <div id="c_nonexistent,cstring">
<dt><pre><span class="Keyword">proc</span> <a href="#c_nonexistent%2Ccstring"><span class="Identifier">c_nonexistent</span></a><span class="Other">(</span><span class="Identifier">frmt</span><span class="Other">:</span> <span class="Identifier">cstring</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">cint</span> {.<span class="Identifier">importc</span><span class="Other">:</span> <span class="StringLit">&quot;nonexistent&quot;</span><span class="Other">,</span> <dt><pre><span class="Keyword">proc</span> <a href="#c_nonexistent%2Ccstring"><span class="Identifier">c_nonexistent</span></a><span class="Other">(</span><span class="Identifier">frmt</span><span class="Other">:</span> <span class="Identifier">cstring</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">cint</span> {.<span class="Identifier">importc</span><span class="Other">:</span> <span class="StringLit">&quot;nonexistent&quot;</span><span class="Other">,</span>
<span class="Identifier">header</span><span class="Other">:</span> <span class="StringLit">&quot;&lt;stdio.h&gt;&quot;</span><span class="Other">,</span> <span class="Identifier">varargs</span><span class="Other">,</span> <span class="Identifier">discardable</span>.}</pre></dt> <span class="Identifier">header</span><span class="Other">:</span> <span class="StringLit">&quot;&lt;stdio.h&gt;&quot;</span><span class="Other">,</span> <span class="Identifier">varargs</span><span class="Other">,</span> <span class="Identifier">discardable</span><span class="Other">,</span> <span><span class="Other pragmadots">...</span></span><span class="pragmawrap"><span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Other">]</span><span class="Other">,</span> <span class="Identifier">tags</span><span class="Other">:</span> <span class="Other">[</span><span class="Other">]</span></span>.}</pre></dt>
<dd> <dd>
@ -663,7 +663,7 @@ This is deprecated with a message.
</div> </div>
<div id="c_printf,cstring"> <div id="c_printf,cstring">
<dt><pre><span class="Keyword">proc</span> <a href="#c_printf%2Ccstring"><span class="Identifier">c_printf</span></a><span class="Other">(</span><span class="Identifier">frmt</span><span class="Other">:</span> <span class="Identifier">cstring</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">cint</span> {.<span class="Identifier">importc</span><span class="Other">:</span> <span class="StringLit">&quot;printf&quot;</span><span class="Other">,</span> <span class="Identifier">header</span><span class="Other">:</span> <span class="StringLit">&quot;&lt;stdio.h&gt;&quot;</span><span class="Other">,</span> <dt><pre><span class="Keyword">proc</span> <a href="#c_printf%2Ccstring"><span class="Identifier">c_printf</span></a><span class="Other">(</span><span class="Identifier">frmt</span><span class="Other">:</span> <span class="Identifier">cstring</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">cint</span> {.<span class="Identifier">importc</span><span class="Other">:</span> <span class="StringLit">&quot;printf&quot;</span><span class="Other">,</span> <span class="Identifier">header</span><span class="Other">:</span> <span class="StringLit">&quot;&lt;stdio.h&gt;&quot;</span><span class="Other">,</span>
<span class="Identifier">varargs</span><span class="Other">,</span> <span class="Identifier">discardable</span>.}</pre></dt> <span class="Identifier">varargs</span><span class="Other">,</span> <span class="Identifier">discardable</span><span class="Other">,</span> <span><span class="Other pragmadots">...</span></span><span class="pragmawrap"><span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Other">]</span><span class="Other">,</span> <span class="Identifier">tags</span><span class="Other">:</span> <span class="Other">[</span><span class="Other">]</span></span>.}</pre></dt>
<dd> <dd>
the c printf. etc. the c printf. etc.
@ -690,7 +690,8 @@ came form utils but should be shown where <tt class="docutils literal"><span cla
</dd> </dd>
</div> </div>
<div id="low2,T"> <div id="low2,T">
<dt><pre><span class="Keyword">proc</span> <a href="#low2%2CT"><span class="Identifier">low2</span></a><span class="Other">[</span><span class="Identifier">T</span><span class="Other">:</span> <span class="Identifier">Ordinal</span> <span class="Operator">|</span> <span class="Keyword">enum</span> <span class="Operator">|</span> <span class="Identifier">range</span><span class="Other">]</span><span class="Other">(</span><span class="Identifier">x</span><span class="Other">:</span> <span class="Identifier">T</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">T</span> {.<span class="Identifier">magic</span><span class="Other">:</span> <span class="StringLit">&quot;Low&quot;</span><span class="Other">,</span> <span class="Identifier">noSideEffect</span>.}</pre></dt> <dt><pre><span class="Keyword">proc</span> <a href="#low2%2CT"><span class="Identifier">low2</span></a><span class="Other">[</span><span class="Identifier">T</span><span class="Other">:</span> <span class="Identifier">Ordinal</span> <span class="Operator">|</span> <span class="Keyword">enum</span> <span class="Operator">|</span> <span class="Identifier">range</span><span class="Other">]</span><span class="Other">(</span><span class="Identifier">x</span><span class="Other">:</span> <span class="Identifier">T</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">T</span> {.<span class="Identifier">magic</span><span class="Other">:</span> <span class="StringLit">&quot;Low&quot;</span><span class="Other">,</span> <span class="Identifier">noSideEffect</span><span class="Other">,</span>
<span><span class="Other pragmadots">...</span></span><span class="pragmawrap"><span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Other">]</span><span class="Other">,</span> <span class="Identifier">tags</span><span class="Other">:</span> <span class="Other">[</span><span class="Other">]</span></span>.}</pre></dt>
<dd> <dd>
<p>Returns the lowest possible value of an ordinal value <tt class="docutils literal"><span class="pre"><span class="Identifier">x</span></span></tt>. As a special semantic rule, <tt class="docutils literal"><span class="pre"><span class="Identifier">x</span></span></tt> may also be a type identifier.</p> <p>Returns the lowest possible value of an ordinal value <tt class="docutils literal"><span class="pre"><span class="Identifier">x</span></span></tt>. As a special semantic rule, <tt class="docutils literal"><span class="pre"><span class="Identifier">x</span></span></tt> may also be a type identifier.</p>
@ -704,7 +705,8 @@ came form utils but should be shown where <tt class="docutils literal"><span cla
</dd> </dd>
</div> </div>
<div id="low,T"> <div id="low,T">
<dt><pre><span class="Keyword">proc</span> <a href="#low%2CT"><span class="Identifier">low</span></a><span class="Other">[</span><span class="Identifier">T</span><span class="Other">:</span> <span class="Identifier">Ordinal</span> <span class="Operator">|</span> <span class="Keyword">enum</span> <span class="Operator">|</span> <span class="Identifier">range</span><span class="Other">]</span><span class="Other">(</span><span class="Identifier">x</span><span class="Other">:</span> <span class="Identifier">T</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">T</span> {.<span class="Identifier">magic</span><span class="Other">:</span> <span class="StringLit">&quot;Low&quot;</span><span class="Other">,</span> <span class="Identifier">noSideEffect</span>.}</pre></dt> <dt><pre><span class="Keyword">proc</span> <a href="#low%2CT"><span class="Identifier">low</span></a><span class="Other">[</span><span class="Identifier">T</span><span class="Other">:</span> <span class="Identifier">Ordinal</span> <span class="Operator">|</span> <span class="Keyword">enum</span> <span class="Operator">|</span> <span class="Identifier">range</span><span class="Other">]</span><span class="Other">(</span><span class="Identifier">x</span><span class="Other">:</span> <span class="Identifier">T</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">T</span> {.<span class="Identifier">magic</span><span class="Other">:</span> <span class="StringLit">&quot;Low&quot;</span><span class="Other">,</span> <span class="Identifier">noSideEffect</span><span class="Other">,</span>
<span><span class="Other pragmadots">...</span></span><span class="pragmawrap"><span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Other">]</span><span class="Other">,</span> <span class="Identifier">tags</span><span class="Other">:</span> <span class="Other">[</span><span class="Other">]</span></span>.}</pre></dt>
<dd> <dd>
<p>Returns the lowest possible value of an ordinal value <tt class="docutils literal"><span class="pre"><span class="Identifier">x</span></span></tt>. As a special semantic rule, <tt class="docutils literal"><span class="pre"><span class="Identifier">x</span></span></tt> may also be a type identifier.</p> <p>Returns the lowest possible value of an ordinal value <tt class="docutils literal"><span class="pre"><span class="Identifier">x</span></span></tt>. As a special semantic rule, <tt class="docutils literal"><span class="pre"><span class="Identifier">x</span></span></tt> may also be a type identifier.</p>

View file

@ -6,7 +6,7 @@ teffects1.nim(22, 29) Hint: 'lier' cannot raise 'IO2Error' [XCannotRaiseY]
teffects1.nim(38, 21) Error: type mismatch: got <proc (x: int): string{.noSideEffect, gcsafe, locks: 0.}> but expected 'MyProcType = proc (x: int): string{.closure.}' teffects1.nim(38, 21) Error: type mismatch: got <proc (x: int): string{.noSideEffect, gcsafe, locks: 0.}> but expected 'MyProcType = proc (x: int): string{.closure.}'
.raise effects differ''' .raise effects differ'''
""" """
{.push warningAsError[Effect]: on.}
type type
TObj {.pure, inheritable.} = object TObj {.pure, inheritable.} = object
TObjB = object of TObj TObjB = object of TObj
@ -41,3 +41,4 @@ type mismatch: got <proc (x: int): string{.noSideEffect, gcsafe, locks: 0.}> but
]# ]#
{.pop.} {.pop.}
{.pop.}

View file

@ -2,7 +2,7 @@ discard """
errormsg: "can raise an unlisted exception: ref IOError" errormsg: "can raise an unlisted exception: ref IOError"
line: 19 line: 19
""" """
{.push warningAsError[Effect]: on.}
type type
TObj = object {.pure, inheritable.} TObj = object {.pure, inheritable.}
TObjB = object of TObj TObjB = object of TObj
@ -17,3 +17,4 @@ proc lier(): int {.raises: [IOError].} =
proc forw: int = proc forw: int =
raise newException(IOError, "arg") raise newException(IOError, "arg")
{.pop.}

View file

@ -2,7 +2,7 @@ discard """
errormsg: "can raise an unlisted exception: ref ValueError" errormsg: "can raise an unlisted exception: ref ValueError"
line: 10 line: 10
""" """
{.push warningAsError[Effect]: on.}
proc foo() {.raises: [].} = proc foo() {.raises: [].} =
try: try:
discard discard
@ -12,3 +12,5 @@ proc foo() {.raises: [].} =
discard discard
foo() foo()
{.pop.}

View file

@ -2,7 +2,7 @@ discard """
errormsg: "can raise an unlisted exception: Exception" errormsg: "can raise an unlisted exception: Exception"
line: 10 line: 10
""" """
{.push warningAsError[Effect]: on.}
proc foo() {.raises: [].} = proc foo() {.raises: [].} =
try: try:
discard discard
@ -10,3 +10,4 @@ proc foo() {.raises: [].} =
raise raise
foo() foo()
{.pop.}

View file

@ -0,0 +1,27 @@
discard """
errormsg: "s1 can raise an unlisted exception: CatchableError"
line: 27
"""
{.push warningAsError[Effect]: on.}
{.experimental: "strictEffects".}
# bug #18376
{.push raises: [Defect].}
type Call = proc (x: int): int {.gcsafe, raises: [Defect, CatchableError].}
type Bar* = object
foo*: Call
proc passOn*(x: Call) = discard
proc barCal(b: var Bar, s: string, s1: Call) =
#compiler complains that his line can throw CatchableError
passOn s1
proc passOnB*(x: Call) {.effectsOf: x.} = discard
proc barCal2(b: var Bar, s: string, s1: Call) =
passOnB s1

View file

@ -0,0 +1,28 @@
discard """
errormsg: "can raise an unlisted exception: Exception"
line: 23
"""
{.push warningAsError[Effect]: on.}
{.experimental: "strictEffects".}
# bug #13905
proc atoi(v: cstring): cint {.importc: "atoi", cdecl, raises: [].}
type Conv = proc(v: cstring): cint {.cdecl, raises: [].}
var x: Conv = atoi
# bug #17475
type
Callback = proc()
proc f(callback: Callback) {.raises: [].} =
callback()
proc main =
f(proc () = raise newException(IOError, "IO"))
main()

View file

@ -0,0 +1,17 @@
discard """
action: compile
"""
{.push warningAsError[Effect]: on.}
{.experimental: "strictEffects".}
proc fn(a: int, p1, p2: proc()) {.effectsOf: p1.} =
if a == 7:
p1()
if a<0:
raise newException(ValueError, $a)
proc main() {.raises: [ValueError].} =
fn(1, proc()=discard, proc() = raise newException(IOError, "foo"))
main()

View file

@ -0,0 +1,27 @@
discard """
errormsg: "cmpE can raise an unlisted exception: Exception"
line: 27
"""
{.push warningAsError[Effect]: on.}
{.experimental: "strictEffects".}
import algorithm
type
MyInt = distinct int
var toSort = @[MyInt 1, MyInt 2, MyInt 3]
proc cmpN(a, b: MyInt): int =
cmp(a.int, b.int)
proc harmless {.raises: [].} =
toSort.sort cmpN
proc cmpE(a, b: MyInt): int {.raises: [Exception].} =
cmp(a.int, b.int)
proc harmfull {.raises: [].} =
toSort.sort cmpE

View file

@ -3,9 +3,10 @@ discard """
file: "tuserpragma2.nim" file: "tuserpragma2.nim"
line: 11 line: 11
""" """
{.push warningAsError[Effect]: on.}
# bug #7216 # bug #7216
{.pragma: my_pragma, raises: [].} {.pragma: my_pragma, raises: [].}
proc test1 {.my_pragma.} = proc test1 {.my_pragma.} =
raise newException(Exception, "msg") raise newException(Exception, "msg")
{.pop.}

View file

@ -20,12 +20,6 @@ Hi Andreas! How do you feel, Rumpf?
@[0, 2, 1] @[0, 2, 1]
@[0, 1, 2] @[0, 1, 2]
055this should be the casehugh@["(", "+", " 1", " 2", ")"] 055this should be the casehugh@["(", "+", " 1", " 2", ")"]
caught a crash!
caught a crash!
caught a crash!
caught a crash!
caught a crash!
caught a crash!
[5] [5]
[4, 5] [4, 5]
[3, 4, 5] [3, 4, 5]
@ -161,18 +155,21 @@ block tropes:
block tsegfaults: block tsegfaults:
proc main = when not defined(arm64):
try: var crashes = 0
var x: ptr int proc main =
echo x[]
try: try:
raise newException(ValueError, "not a crash") var x: ptr int
except ValueError: echo x[]
discard try:
except NilAccessDefect: raise newException(ValueError, "not a crash")
echo "caught a crash!" except ValueError:
for i in 0..5: discard
main() except NilAccessDefect:
inc crashes
for i in 0..5:
main()
assert crashes == 6