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
parent 72fa5833ad
commit e0ef859130
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`.
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

View File

@@ -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):

View File

@@ -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:

View File

@@ -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")

View File

@@ -137,3 +137,4 @@ proc initDefines*(symbols: StringTableRef) =
defineSymbol("nimHasDragonBox")
defineSymbol("nimHasHintAll")
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]
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)

View File

@@ -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`

View File

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

View File

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

View File

@@ -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})

View File

@@ -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 =

View File

@@ -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."

View File

@@ -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

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
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):

View File

@@ -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)

View File

@@ -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 =

View File

@@ -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",

View File

@@ -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
----------------

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))`".} =
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]]

View File

@@ -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 =

View File

@@ -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

View File

@@ -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 {.

View File

@@ -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

View File

@@ -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

View File

@@ -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">&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>
@@ -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">&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>
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">&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>
<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">&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>
<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.}'
.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.}

View File

@@ -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.}

View File

@@ -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.}

View File

@@ -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.}

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"
line: 11
"""
{.push warningAsError[Effect]: on.}
# bug #7216
{.pragma: my_pragma, raises: [].}
proc test1 {.my_pragma.} =
raise newException(Exception, "msg")
{.pop.}

View File

@@ -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