mirror of
https://github.com/nim-lang/Nim.git
synced 2026-06-08 04:44:20 +00:00
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:
17
changelog.md
17
changelog.md
@@ -427,6 +427,23 @@
|
||||
- On embedded devices `malloc` can now be used instead of `mmap` via `-d:nimAllocPagesViaMalloc`.
|
||||
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
|
||||
|
||||
|
||||
@@ -229,7 +229,7 @@ type
|
||||
TNodeKinds* = set[TNodeKind]
|
||||
|
||||
type
|
||||
TSymFlag* = enum # 47 flags!
|
||||
TSymFlag* = enum # 48 flags!
|
||||
sfUsed, # read access of sym (for warnings) or simply used
|
||||
sfExported, # symbol is exported from module
|
||||
sfFromGeneric, # symbol is instantiation of a generic; this is needed
|
||||
@@ -299,6 +299,7 @@ type
|
||||
sfUsedInFinallyOrExcept # symbol is used inside an 'except' or 'finally'
|
||||
sfSingleUsedTemp # For temporaries that we know will only be used once
|
||||
sfNoalias # 'noalias' annotation, means C's 'restrict'
|
||||
sfEffectsDelayed # an 'effectsDelayed' parameter
|
||||
|
||||
TSymFlags* = set[TSymFlag]
|
||||
|
||||
@@ -568,6 +569,7 @@ type
|
||||
# sizeof, alignof, offsetof at CT
|
||||
tfExplicitCallConv
|
||||
tfIsConstructor
|
||||
tfEffectSystemWorkaround
|
||||
|
||||
TTypeFlags* = set[TTypeFlag]
|
||||
|
||||
@@ -1781,7 +1783,7 @@ proc containsNode*(n: PNode, kinds: TNodeKinds): bool =
|
||||
|
||||
proc hasSubnodeWith*(n: PNode, kind: TNodeKind): bool =
|
||||
case n.kind
|
||||
of nkEmpty..nkNilLit: result = n.kind == kind
|
||||
of nkEmpty..nkNilLit, nkFormalParams: result = n.kind == kind
|
||||
else:
|
||||
for i in 0..<n.len:
|
||||
if (n[i].kind == kind) or hasSubnodeWith(n[i], kind):
|
||||
|
||||
@@ -325,7 +325,7 @@ proc skipTrivialIndirections(n: PNode): PNode =
|
||||
|
||||
proc getPotentialWrites(n: PNode; mutate: bool; result: var seq[PNode]) =
|
||||
case n.kind:
|
||||
of nkLiterals, nkIdent: discard
|
||||
of nkLiterals, nkIdent, nkFormalParams: discard
|
||||
of nkSym:
|
||||
if mutate: result.add n
|
||||
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]) =
|
||||
case n.kind:
|
||||
of nkLiterals, nkIdent: discard
|
||||
of nkLiterals, nkIdent, nkFormalParams: discard
|
||||
of nkSym: result.add n
|
||||
else:
|
||||
for s in n:
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#
|
||||
|
||||
# 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
|
||||
# confused with 'TGCMode.gcMarkAndSweep' etc.
|
||||
@@ -30,7 +29,7 @@ import
|
||||
msgs, options, nversion, condsyms, extccomp, platform,
|
||||
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.
|
||||
bootSwitch(usedTinyC, hasTinyCBackend, "-d:tinyc")
|
||||
|
||||
@@ -137,3 +137,4 @@ proc initDefines*(symbols: StringTableRef) =
|
||||
defineSymbol("nimHasDragonBox")
|
||||
defineSymbol("nimHasHintAll")
|
||||
defineSymbol("nimHasTrace")
|
||||
defineSymbol("nimHasEffectsOf")
|
||||
|
||||
@@ -1085,10 +1085,11 @@ proc documentEffect(cache: IdentCache; n, x: PNode, effectType: TSpecialWord, id
|
||||
let actual = s.typ.n[0]
|
||||
if actual.len != effectListLen: return
|
||||
let real = actual[idx]
|
||||
|
||||
if real == nil: return
|
||||
let realLen = real.len
|
||||
# warning: hack ahead:
|
||||
var effects = newNodeI(nkBracket, n.info, real.len)
|
||||
for i in 0..<real.len:
|
||||
var effects = newNodeI(nkBracket, n.info, realLen)
|
||||
for i in 0..<realLen:
|
||||
var t = typeToString(real[i].typ)
|
||||
if t.startsWith("ref "): t = substr(t, 4)
|
||||
effects[i] = newIdentNode(getIdent(cache, t), n.info)
|
||||
|
||||
@@ -75,6 +75,7 @@ type
|
||||
warnAnyEnumConv = "AnyEnumConv",
|
||||
warnHoleEnumConv = "HoleEnumConv",
|
||||
warnCstringConv = "CStringConv",
|
||||
warnEffect = "Effect",
|
||||
warnUser = "User",
|
||||
# hints
|
||||
hintSuccess = "Success", hintSuccessX = "SuccessX",
|
||||
@@ -163,6 +164,7 @@ const
|
||||
warnAnyEnumConv: "$1",
|
||||
warnHoleEnumConv: "$1",
|
||||
warnCstringConv: "$1",
|
||||
warnEffect: "$1",
|
||||
warnUser: "$1",
|
||||
hintSuccess: "operation successful: $#",
|
||||
# keep in sync with `testament.isSuccess`
|
||||
|
||||
@@ -25,3 +25,8 @@ define:useStdoutAsStdmsg
|
||||
@if nimHasWarningObservableStores:
|
||||
warning:ObservableStores: off
|
||||
@end
|
||||
|
||||
@if nimHasEffectsOf:
|
||||
experimental:strictEffects
|
||||
warningAsError:Effect:on
|
||||
@end
|
||||
|
||||
@@ -204,7 +204,8 @@ type
|
||||
strictFuncs,
|
||||
views,
|
||||
strictNotNil,
|
||||
overloadableEnums
|
||||
overloadableEnums,
|
||||
strictEffects
|
||||
|
||||
LegacyFeature* = enum
|
||||
allowSemcheckedAstModification,
|
||||
|
||||
@@ -29,7 +29,7 @@ const
|
||||
wCompilerProc, wNonReloadable, wCore, wProcVar, wVarargs, wCompileTime, wMerge,
|
||||
wBorrow, wImportCompilerProc, wThread,
|
||||
wAsmNoStackFrame, wDiscardable, wNoInit, wCodegenDecl,
|
||||
wGensym, wInject, wRaises, wTags, wLocks, wDelegator, wGcSafe,
|
||||
wGensym, wInject, wRaises, wEffectsOf, wTags, wLocks, wDelegator, wGcSafe,
|
||||
wConstructor, wLiftLocals, wStackTrace, wLineTrace, wNoDestroy,
|
||||
wRequires, wEnsures}
|
||||
converterPragmas* = procPragmas
|
||||
@@ -41,7 +41,7 @@ const
|
||||
wDiscardable, wGensym, wInject, wDelegator}
|
||||
iteratorPragmas* = declPragmas + {FirstCallConv..LastCallConv, wNoSideEffect, wSideEffect,
|
||||
wMagic, wBorrow,
|
||||
wDiscardable, wGensym, wInject, wRaises,
|
||||
wDiscardable, wGensym, wInject, wRaises, wEffectsOf,
|
||||
wTags, wLocks, wGcSafe, wRequires, wEnsures}
|
||||
exprPragmas* = {wLine, wLocks, wNoRewrite, wGcSafe, wNoSideEffect}
|
||||
stmtPragmas* = {wChecks, wObjChecks, wFieldChecks, wRangeChecks,
|
||||
@@ -59,7 +59,7 @@ const
|
||||
lambdaPragmas* = {FirstCallConv..LastCallConv,
|
||||
wNoSideEffect, wSideEffect, wNoreturn, wNosinks, wDynlib, wHeader,
|
||||
wThread, wAsmNoStackFrame,
|
||||
wRaises, wLocks, wTags, wRequires, wEnsures,
|
||||
wRaises, wLocks, wTags, wRequires, wEnsures, wEffectsOf,
|
||||
wGcSafe, wCodegenDecl, wNoInit, wCompileTime}
|
||||
typePragmas* = declPragmas + {wMagic, wAcyclic,
|
||||
wPure, wHeader, wCompilerProc, wCore, wFinal, wSize, wShallow,
|
||||
@@ -79,7 +79,7 @@ const
|
||||
paramPragmas* = {wNoalias, wInject, wGensym}
|
||||
letPragmas* = varPragmas
|
||||
procTypePragmas* = {FirstCallConv..LastCallConv, wVarargs, wNoSideEffect,
|
||||
wThread, wRaises, wLocks, wTags, wGcSafe,
|
||||
wThread, wRaises, wEffectsOf, wLocks, wTags, wGcSafe,
|
||||
wRequires, wEnsures}
|
||||
forVarPragmas* = {wInject, wGensym}
|
||||
allRoutinePragmas* = methodPragmas + iteratorPragmas + lambdaPragmas
|
||||
@@ -779,6 +779,26 @@ proc semCustomPragma(c: PContext, n: PNode): PNode =
|
||||
# pragma(arg) -> pragma: arg
|
||||
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,
|
||||
validPragmas: TSpecialWords,
|
||||
comesFromPush, isStatement: bool): bool =
|
||||
@@ -895,6 +915,8 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
|
||||
of wNoalias:
|
||||
noVal(c, it)
|
||||
incl(sym.flags, sfNoalias)
|
||||
of wEffectsOf:
|
||||
processEffectsOf(c, it, sym)
|
||||
of wThreadVar:
|
||||
noVal(c, it)
|
||||
incl(sym.flags, {sfThread, sfGlobal})
|
||||
|
||||
@@ -400,9 +400,10 @@ when not defined(nimHasSinkInference):
|
||||
include hlo, seminst, semcall
|
||||
|
||||
proc resetSemFlag(n: PNode) =
|
||||
excl n.flags, nfSem
|
||||
for i in 0..<n.safeLen:
|
||||
resetSemFlag(n[i])
|
||||
if n != nil:
|
||||
excl n.flags, nfSem
|
||||
for i in 0..<n.safeLen:
|
||||
resetSemFlag(n[i])
|
||||
|
||||
proc semAfterMacroCall(c: PContext, call, macroResult: PNode,
|
||||
s: PSym, flags: TExprFlags): PNode =
|
||||
|
||||
@@ -152,6 +152,8 @@ proc effectProblem(f, a: PType; result: var string; c: PContext) =
|
||||
of efLockLevelsDiffer:
|
||||
result.add "\n The `.locks` requirements differ. Annotate the " &
|
||||
"proc with {.locks: 0.} to get extended error information."
|
||||
of efEffectsDelayed:
|
||||
result.add "\n The `.effectsOf` annotations differ."
|
||||
when defined(drnim):
|
||||
if not c.graph.compatibleProps(c.graph, f, a):
|
||||
result.add "\n The `.requires` or `.ensures` properties are incompatible."
|
||||
|
||||
@@ -261,7 +261,9 @@ proc getGenSym*(c: PContext; s: PSym): PSym =
|
||||
result = s
|
||||
|
||||
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)
|
||||
if n.sym != s:
|
||||
n.sym = s
|
||||
|
||||
@@ -296,11 +296,12 @@ proc useVarNoInitCheck(a: PEffects; n: PNode; s: PSym) =
|
||||
if {sfGlobal, sfThread} * s.flags != {} and s.kind in {skVar, skLet} and
|
||||
s.magic != mNimvm:
|
||||
if s.guard != nil: guardGlobal(a, n, s.guard)
|
||||
if {sfGlobal, sfThread} * s.flags == {sfGlobal} and
|
||||
(tfHasGCedMem in s.typ.flags or s.typ.isGCedMem):
|
||||
#if a.config.hasWarn(warnGcUnsafe): warnAboutGcUnsafe(n)
|
||||
markGcUnsafe(a, s)
|
||||
markSideEffect(a, s, n.info)
|
||||
if strictEffects notin a.c.features:
|
||||
if {sfGlobal, sfThread} * s.flags == {sfGlobal} and
|
||||
(tfHasGCedMem in s.typ.flags or s.typ.isGCedMem):
|
||||
#if a.config.hasWarn(warnGcUnsafe): warnAboutGcUnsafe(n)
|
||||
markGcUnsafe(a, s)
|
||||
markSideEffect(a, s, n.info)
|
||||
if s.owner != a.owner and s.kind in {skVar, skLet, skForVar, skResult, skParam} and
|
||||
{sfGlobal, sfThread} * s.flags == {}:
|
||||
a.isInnerProc = true
|
||||
@@ -474,14 +475,20 @@ proc trackTryStmt(tracked: PEffects, n: PNode) =
|
||||
for id, count in items(inter):
|
||||
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.
|
||||
# Instead we track expressions of type tyProc too. See the manual for
|
||||
# details:
|
||||
if n.kind != nkSym:
|
||||
result = true
|
||||
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:
|
||||
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")
|
||||
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
|
||||
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.} =
|
||||
assert n.kind == nkEffectList
|
||||
@@ -590,11 +602,15 @@ proc isNoEffectList(n: PNode): bool {.inline.} =
|
||||
proc isTrival(caller: PNode): bool {.inline.} =
|
||||
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 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:
|
||||
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
|
||||
var effectList = op.n[0]
|
||||
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
|
||||
# stated there are no additional effects, so simply propagate them:
|
||||
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:
|
||||
assumeTheWorst(tracked, n, op)
|
||||
# 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)
|
||||
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)
|
||||
else:
|
||||
mergeRaises(tracked, effectList[exceptionEffects], n)
|
||||
@@ -624,6 +640,7 @@ proc trackOperandForIndirectCall(tracked: PEffects, n: PNode, paramType: PType;
|
||||
markGcUnsafe(tracked, a)
|
||||
elif tfNoSideEffect notin op.flags:
|
||||
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}:
|
||||
invalidateFacts(tracked.guards, n)
|
||||
if n.kind == nkSym and isLocalVar(tracked, n.sym):
|
||||
@@ -724,9 +741,6 @@ proc trackBlock(tracked: PEffects, n: PNode) =
|
||||
else:
|
||||
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) =
|
||||
if n[0].typ.kind == tyCstring and (let a = skipConv(n[1]);
|
||||
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, 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) =
|
||||
template gcsafeAndSideeffectCheck() =
|
||||
if notGcSafe(op) and not importedFromC(a):
|
||||
@@ -811,7 +844,7 @@ proc trackCall(tracked: PEffects; n: PNode) =
|
||||
elif isNoEffectList(effectList):
|
||||
if isForwardedProc(a):
|
||||
propagateEffects(tracked, n, a.sym)
|
||||
elif isIndirectCall(a, tracked.owner):
|
||||
elif isIndirectCall(tracked, a):
|
||||
assumeTheWorst(tracked, n, op)
|
||||
gcsafeAndSideeffectCheck()
|
||||
else:
|
||||
@@ -819,7 +852,8 @@ proc trackCall(tracked: PEffects; n: PNode) =
|
||||
mergeTags(tracked, effectList[tagEffects], n)
|
||||
gcsafeAndSideeffectCheck()
|
||||
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}:
|
||||
# may not look like an assignment, but it is:
|
||||
let arg = n[1]
|
||||
@@ -1225,7 +1259,7 @@ proc subtypeRelation(g: ModuleGraph; spec, real: PNode): bool =
|
||||
else:
|
||||
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.};
|
||||
hintsArg: PNode = nil) =
|
||||
# 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)
|
||||
var rr = if r.kind == nkRaiseStmt: r[0] else: r
|
||||
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)
|
||||
# hint about unnecessarily listed exception types:
|
||||
if hints:
|
||||
@@ -1258,11 +1293,11 @@ proc checkMethodEffects*(g: ModuleGraph; disp, branch: PSym) =
|
||||
let p = disp.ast[pragmasPos]
|
||||
let raisesSpec = effectSpec(p, wRaises)
|
||||
if not isNil(raisesSpec):
|
||||
checkRaisesSpec(g, raisesSpec, actual[exceptionEffects],
|
||||
checkRaisesSpec(g, false, raisesSpec, actual[exceptionEffects],
|
||||
"can raise an unlisted exception: ", hints=off, subtypeRelation)
|
||||
let tagsSpec = effectSpec(p, wTags)
|
||||
if not isNil(tagsSpec):
|
||||
checkRaisesSpec(g, tagsSpec, actual[tagEffects],
|
||||
checkRaisesSpec(g, false, tagsSpec, actual[tagEffects],
|
||||
"can have an unlisted effect: ", hints=off, subtypeRelation)
|
||||
if sfThread in disp.flags and notGcSafe(branch.typ):
|
||||
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" %
|
||||
[$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]
|
||||
if t.kind != tyProc or effects.kind != nkEffectList: return
|
||||
if n.kind != nkEmpty:
|
||||
@@ -1292,9 +1327,14 @@ proc setEffectsForProcType*(g: ModuleGraph; t: PType, n: PNode) =
|
||||
let raisesSpec = effectSpec(n, wRaises)
|
||||
if not isNil(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)
|
||||
if not isNil(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)
|
||||
if not isNil(requiresSpec):
|
||||
@@ -1304,15 +1344,21 @@ proc setEffectsForProcType*(g: ModuleGraph; t: PType, n: PNode) =
|
||||
effects[ensuresEffects] = ensuresSpec
|
||||
|
||||
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)
|
||||
effects[exceptionEffects] = newNodeI(nkArgList, s.info)
|
||||
effects[tagEffects] = newNodeI(nkArgList, s.info)
|
||||
effects[exceptionEffects] = newNodeI(nkArgList, effects.info)
|
||||
effects[tagEffects] = newNodeI(nkArgList, effects.info)
|
||||
effects[requiresEffects] = g.emptyNode
|
||||
effects[ensuresEffects] = 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.tags = effects[tagEffects]
|
||||
t.owner = s
|
||||
@@ -1341,10 +1387,14 @@ proc trackProc*(c: PContext; s: PSym, body: PNode) =
|
||||
if effects.kind != nkEffectList: return
|
||||
# effects already computed?
|
||||
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
|
||||
initEffects(g, effects, s, t, c)
|
||||
initEffects(g, inferredEffects, s, t, c)
|
||||
rawInitEffects g, effects
|
||||
track(t, body)
|
||||
|
||||
if s.kind != skMacro:
|
||||
@@ -1369,17 +1419,21 @@ proc trackProc*(c: PContext; s: PSym, body: PNode) =
|
||||
let p = s.ast[pragmasPos]
|
||||
let raisesSpec = effectSpec(p, wRaises)
|
||||
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])
|
||||
# after the check, use the formal spec:
|
||||
effects[exceptionEffects] = raisesSpec
|
||||
else:
|
||||
effects[exceptionEffects] = t.exc
|
||||
|
||||
let tagsSpec = effectSpec(p, wTags)
|
||||
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)
|
||||
# after the check, use the formal spec:
|
||||
effects[tagEffects] = tagsSpec
|
||||
else:
|
||||
effects[tagEffects] = t.tags
|
||||
|
||||
let requiresSpec = propSpec(p, wRequires)
|
||||
if not isNil(requiresSpec):
|
||||
|
||||
@@ -1953,6 +1953,10 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
|
||||
if not hasProto:
|
||||
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
|
||||
# allow a bit redundancy in the pragma declarations. The rule is
|
||||
# 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)
|
||||
|
||||
proc setLine(n: PNode, info: TLineInfo) =
|
||||
for i in 0..<n.safeLen: setLine(n[i], info)
|
||||
n.info = info
|
||||
if n != nil:
|
||||
for i in 0..<n.safeLen: setLine(n[i], info)
|
||||
n.info = info
|
||||
|
||||
proc semPragmaBlock(c: PContext, n: PNode): PNode =
|
||||
checkSonsLen(n, 2, c.config)
|
||||
|
||||
@@ -1364,10 +1364,14 @@ type
|
||||
efTagsDiffer
|
||||
efTagsUnknown
|
||||
efLockLevelsDiffer
|
||||
efEffectsDelayed
|
||||
|
||||
proc compatibleEffects*(formal, actual: PType): EffectsCompat =
|
||||
# for proc type compatibility checking:
|
||||
assert formal.kind == tyProc and actual.kind == tyProc
|
||||
#if tfEffectSystemWorkaround in actual.flags:
|
||||
# return efCompat
|
||||
|
||||
if formal.n[0].kind != nkEffectList or
|
||||
actual.n[0].kind != nkEffectList:
|
||||
return efTagsUnknown
|
||||
@@ -1391,9 +1395,17 @@ proc compatibleEffects*(formal, actual: PType): EffectsCompat =
|
||||
# spec requires some exception or tag, but we don't know anything:
|
||||
if real.len == 0: return efTagsUnknown
|
||||
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
|
||||
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
|
||||
else:
|
||||
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'"
|
||||
of efLockLevelsDiffer:
|
||||
msg.add "\nlock levels differ"
|
||||
|
||||
of efEffectsDelayed:
|
||||
msg.add "\n.effectsOf annotations differ"
|
||||
localError(conf, info, msg)
|
||||
|
||||
proc isTupleRecursive(t: PType, cycleDetector: var IntSet): bool =
|
||||
|
||||
@@ -35,7 +35,7 @@ type
|
||||
wMagic = "magic", wThread = "thread", wFinal = "final", wProfiler = "profiler",
|
||||
wMemTracker = "memtracker", wObjChecks = "objchecks",
|
||||
wIntDefine = "intdefine", wStrDefine = "strdefine", wBoolDefine = "booldefine",
|
||||
wCursor = "cursor", wNoalias = "noalias",
|
||||
wCursor = "cursor", wNoalias = "noalias", wEffectsOf = "effectsOf",
|
||||
|
||||
wImmediate = "immediate", wConstructor = "constructor", wDestructor = "destructor",
|
||||
wDelegator = "delegator", wOverride = "override", wImportCpp = "importcpp",
|
||||
|
||||
252
doc/manual.rst
252
doc/manual.rst
@@ -4721,6 +4721,11 @@ and rely on functionality of the `x` object to get exception details.
|
||||
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
|
||||
------------------
|
||||
|
||||
@@ -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
|
||||
raise `system.Exception` (the base type of the exception hierarchy) and
|
||||
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.
|
||||
2. Every expression of some proc type within a call that is not a call
|
||||
itself (and not nil) is assumed to be called indirectly somehow and thus
|
||||
However, if the call is of the form `f(...)` where `f` is a parameter of
|
||||
the currently analyzed routine it is ignored that is marked as `.effectsOf: f`.
|
||||
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.
|
||||
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.
|
||||
Procs that are `importc`'ed are assumed to have `.raises: []`, unless explicitly
|
||||
declared otherwise.
|
||||
4. Every call to a method `m` is assumed to
|
||||
raise `system.Exception` unless `m` has an explicit `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
|
||||
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
|
||||
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.)
|
||||
|
||||
|
||||
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
|
||||
------------
|
||||
|
||||
@@ -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:
|
||||
|
||||
.. code-block:: nim
|
||||
:test: "nim c $1"
|
||||
:test: "nim c --warningAsError:Effect:on $1"
|
||||
:status: 1
|
||||
|
||||
type IO = object ## input/output effect
|
||||
@@ -4848,6 +4896,78 @@ The inference for tag tracking is analogous to the inference for
|
||||
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
|
||||
--------------
|
||||
@@ -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".}
|
||||
|
||||
|
||||
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
|
||||
------------------
|
||||
@@ -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,
|
||||
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
|
||||
-------------
|
||||
@@ -7875,43 +7950,6 @@ A thread proc is passed to `createThread` or `spawn` and invoked
|
||||
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
|
||||
----------------
|
||||
|
||||
@@ -147,8 +147,13 @@ proc reversed*[T](a: openArray[T], first: Natural, last: int): seq[T]
|
||||
{.inline, deprecated: "use: `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,
|
||||
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.
|
||||
## Assumes that `a` is sorted according to `cmp`.
|
||||
##
|
||||
@@ -210,7 +215,7 @@ const
|
||||
onlySafeCode = true
|
||||
|
||||
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
|
||||
## (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
|
||||
@@ -260,7 +265,7 @@ proc lowerBound*[T](a: openArray[T], key: T): int = lowerBound(a, key, cmp[T])
|
||||
## * `upperBound proc<#upperBound,openArray[T],T>`_
|
||||
|
||||
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
|
||||
## `key`, or last if no such element is found.
|
||||
## 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))
|
||||
|
||||
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!
|
||||
# 1 2 3 4 ## 5 6 7 8
|
||||
# -> 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],
|
||||
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
|
||||
## 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).
|
||||
@@ -420,7 +425,7 @@ proc sort*[T](a: var openArray[T], order = SortOrder.Ascending) = sort[T](a,
|
||||
## * `sortedByIt template<#sortedByIt.t,untyped,untyped>`_
|
||||
|
||||
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`.
|
||||
##
|
||||
## **See also:**
|
||||
@@ -497,7 +502,7 @@ template sortedByIt*(seq1, op: untyped): untyped =
|
||||
|
||||
func isSorted*[T](a: openArray[T],
|
||||
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`
|
||||
## using `cmp` for the comparison. The parameters are identical
|
||||
## to `sort`. Requires O(n) time.
|
||||
@@ -545,7 +550,7 @@ proc isSorted*[T](a: openArray[T], order = SortOrder.Ascending): bool =
|
||||
proc merge*[T](
|
||||
result: var seq[T],
|
||||
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.
|
||||
## If you do not wish to provide your own `cmp`,
|
||||
## you may use `system.cmp` or instead call the overloaded
|
||||
@@ -638,7 +643,7 @@ proc product*[T](x: openArray[seq[T]]): seq[seq[T]] =
|
||||
## Produces the Cartesian product of the array.
|
||||
## Every element of the result is a combination of one element from each seq in `x`,
|
||||
## with the ith element coming from `x[i]`.
|
||||
##
|
||||
##
|
||||
## .. warning:: complexity may explode.
|
||||
runnableExamples:
|
||||
assert product(@[@[1], @[2]]) == @[@[1, 2]]
|
||||
|
||||
@@ -1333,7 +1333,7 @@ proc now*(): DateTime {.tags: [TimeEffect], benign.} =
|
||||
## `cpuTime` instead, depending on the use case.
|
||||
getTime().local
|
||||
|
||||
proc dateTime*(year: int, month: Month, monthday: MonthdayRange,
|
||||
proc dateTime*(year: int, month: Month, monthday: MonthdayRange,
|
||||
hour: HourRange = 0, minute: MinuteRange = 0, second: SecondRange = 0,
|
||||
nanosecond: NanosecondRange = 0,
|
||||
zone: Timezone = local()): DateTime =
|
||||
|
||||
@@ -8,13 +8,18 @@ import os
|
||||
when defined(windows):
|
||||
from strutils import replace
|
||||
|
||||
when defined(nimHasEffectsOf):
|
||||
{.experimental: "strictEffects".}
|
||||
else:
|
||||
{.pragma: effectsOf.}
|
||||
|
||||
type
|
||||
PathEntry* = object
|
||||
kind*: PathComponent
|
||||
path*: string
|
||||
|
||||
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`.
|
||||
#[
|
||||
note: a yieldFilter isn't needed because caller can filter at call site, without
|
||||
|
||||
@@ -78,7 +78,7 @@ proc c_fputs(c: cstring, f: File): cint {.
|
||||
proc c_fgets(c: cstring, n: cint, f: File): cstring {.
|
||||
importc: "fgets", header: "<stdio.h>", tags: [ReadIOEffect].}
|
||||
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 {.
|
||||
importc: "ungetc", header: "<stdio.h>", tags: [].}
|
||||
proc c_putc(c: cint, stream: File): cint {.
|
||||
|
||||
@@ -261,7 +261,7 @@ proc cpDir*(`from`, to: string) {.raises: [OSError].} =
|
||||
checkOsError()
|
||||
|
||||
proc exec*(command: string) {.
|
||||
raises: [OSError], tags: [ExecIOEffect].} =
|
||||
raises: [OSError], tags: [ExecIOEffect, WriteIOEffect].} =
|
||||
## Executes an external process. If the external process terminates with
|
||||
## a non-zero exit code, an OSError exception is raised.
|
||||
##
|
||||
@@ -274,7 +274,7 @@ proc exec*(command: string) {.
|
||||
checkOsError()
|
||||
|
||||
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
|
||||
## a non-zero exit code, an OSError exception is raised.
|
||||
log "exec: " & command:
|
||||
@@ -284,7 +284,7 @@ proc exec*(command: string, input: string, cache = "") {.
|
||||
echo output
|
||||
|
||||
proc selfExec*(command: string) {.
|
||||
raises: [OSError], tags: [ExecIOEffect].} =
|
||||
raises: [OSError], tags: [ExecIOEffect, WriteIOEffect].} =
|
||||
## Executes an external command with the current nim/nimble executable.
|
||||
## `Command` must not contain the "nim " part.
|
||||
let c = selfExe() & " " & command
|
||||
|
||||
@@ -122,7 +122,7 @@ window.addEventListener('DOMContentLoaded', main);
|
||||
<h1><a class="toc-backref" href="#12">Procs</a></h1>
|
||||
<dl class="item">
|
||||
<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>
|
||||
|
||||
I do foo
|
||||
|
||||
@@ -654,7 +654,7 @@ This is deprecated with a message.
|
||||
</div>
|
||||
<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">"nonexistent"</span><span class="Other">,</span>
|
||||
<span class="Identifier">header</span><span class="Other">:</span> <span class="StringLit">"<stdio.h>"</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">"<stdio.h>"</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>
|
||||
|
||||
|
||||
@@ -663,7 +663,7 @@ This is deprecated with a message.
|
||||
</div>
|
||||
<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">"printf"</span><span class="Other">,</span> <span class="Identifier">header</span><span class="Other">:</span> <span class="StringLit">"<stdio.h>"</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>
|
||||
|
||||
the c printf. etc.
|
||||
@@ -690,7 +690,8 @@ came form utils but should be shown where <tt class="docutils literal"><span cla
|
||||
</dd>
|
||||
</div>
|
||||
<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">"Low"</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">"Low"</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>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
<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">"Low"</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">"Low"</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>
|
||||
|
||||
<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>
|
||||
|
||||
@@ -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.}'
|
||||
.raise effects differ'''
|
||||
"""
|
||||
|
||||
{.push warningAsError[Effect]: on.}
|
||||
type
|
||||
TObj {.pure, inheritable.} = object
|
||||
TObjB = object of TObj
|
||||
@@ -41,3 +41,4 @@ type mismatch: got <proc (x: int): string{.noSideEffect, gcsafe, locks: 0.}> but
|
||||
|
||||
]#
|
||||
{.pop.}
|
||||
{.pop.}
|
||||
|
||||
@@ -2,7 +2,7 @@ discard """
|
||||
errormsg: "can raise an unlisted exception: ref IOError"
|
||||
line: 19
|
||||
"""
|
||||
|
||||
{.push warningAsError[Effect]: on.}
|
||||
type
|
||||
TObj = object {.pure, inheritable.}
|
||||
TObjB = object of TObj
|
||||
@@ -17,3 +17,4 @@ proc lier(): int {.raises: [IOError].} =
|
||||
|
||||
proc forw: int =
|
||||
raise newException(IOError, "arg")
|
||||
{.pop.}
|
||||
|
||||
@@ -2,7 +2,7 @@ discard """
|
||||
errormsg: "can raise an unlisted exception: ref ValueError"
|
||||
line: 10
|
||||
"""
|
||||
|
||||
{.push warningAsError[Effect]: on.}
|
||||
proc foo() {.raises: [].} =
|
||||
try:
|
||||
discard
|
||||
@@ -12,3 +12,5 @@ proc foo() {.raises: [].} =
|
||||
discard
|
||||
|
||||
foo()
|
||||
|
||||
{.pop.}
|
||||
|
||||
@@ -2,7 +2,7 @@ discard """
|
||||
errormsg: "can raise an unlisted exception: Exception"
|
||||
line: 10
|
||||
"""
|
||||
|
||||
{.push warningAsError[Effect]: on.}
|
||||
proc foo() {.raises: [].} =
|
||||
try:
|
||||
discard
|
||||
@@ -10,3 +10,4 @@ proc foo() {.raises: [].} =
|
||||
raise
|
||||
|
||||
foo()
|
||||
{.pop.}
|
||||
|
||||
27
tests/effects/tstrict_effects.nim
Normal file
27
tests/effects/tstrict_effects.nim
Normal 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
|
||||
28
tests/effects/tstrict_effects2.nim
Normal file
28
tests/effects/tstrict_effects2.nim
Normal 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()
|
||||
17
tests/effects/tstrict_effects3.nim
Normal file
17
tests/effects/tstrict_effects3.nim
Normal 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()
|
||||
27
tests/effects/tstrict_effects_sort.nim
Normal file
27
tests/effects/tstrict_effects_sort.nim
Normal 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
|
||||
@@ -3,9 +3,10 @@ discard """
|
||||
file: "tuserpragma2.nim"
|
||||
line: 11
|
||||
"""
|
||||
|
||||
{.push warningAsError[Effect]: on.}
|
||||
# bug #7216
|
||||
{.pragma: my_pragma, raises: [].}
|
||||
|
||||
proc test1 {.my_pragma.} =
|
||||
raise newException(Exception, "msg")
|
||||
{.pop.}
|
||||
|
||||
@@ -20,12 +20,6 @@ Hi Andreas! How do you feel, Rumpf?
|
||||
@[0, 2, 1]
|
||||
@[0, 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]
|
||||
[4, 5]
|
||||
[3, 4, 5]
|
||||
@@ -161,18 +155,21 @@ block tropes:
|
||||
|
||||
|
||||
block tsegfaults:
|
||||
proc main =
|
||||
try:
|
||||
var x: ptr int
|
||||
echo x[]
|
||||
when not defined(arm64):
|
||||
var crashes = 0
|
||||
proc main =
|
||||
try:
|
||||
raise newException(ValueError, "not a crash")
|
||||
except ValueError:
|
||||
discard
|
||||
except NilAccessDefect:
|
||||
echo "caught a crash!"
|
||||
for i in 0..5:
|
||||
main()
|
||||
var x: ptr int
|
||||
echo x[]
|
||||
try:
|
||||
raise newException(ValueError, "not a crash")
|
||||
except ValueError:
|
||||
discard
|
||||
except NilAccessDefect:
|
||||
inc crashes
|
||||
for i in 0..5:
|
||||
main()
|
||||
assert crashes == 6
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user