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:
parent
72fa5833ad
commit
e0ef859130
35 changed files with 482 additions and 203 deletions
17
changelog.md
17
changelog.md
|
|
@ -427,6 +427,23 @@
|
||||||
- On embedded devices `malloc` can now be used instead of `mmap` via `-d:nimAllocPagesViaMalloc`.
|
- On embedded devices `malloc` can now be used instead of `mmap` via `-d:nimAllocPagesViaMalloc`.
|
||||||
This is only supported for `--gc:orc` or `--gc:arc`.
|
This is only supported for `--gc:orc` or `--gc:arc`.
|
||||||
|
|
||||||
|
- The effect system was refined and there is a new `.effectsOf` annotation that does
|
||||||
|
explicitly what was previously done implicitly. See the manual for details.
|
||||||
|
To write code that is portable with older Nim versions, use this idiom:
|
||||||
|
|
||||||
|
```nim
|
||||||
|
|
||||||
|
when defined(nimHasEffectsOf):
|
||||||
|
{.experimental: "strictEffects".}
|
||||||
|
else:
|
||||||
|
{.pragma: effectsOf.}
|
||||||
|
|
||||||
|
proc mysort(s: seq; cmp: proc(a, b: T): int) {.effectsOf: cmp.}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
To enable the new effect system, use --experimental:strictEffects.
|
||||||
|
|
||||||
|
|
||||||
## Compiler changes
|
## Compiler changes
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -229,7 +229,7 @@ type
|
||||||
TNodeKinds* = set[TNodeKind]
|
TNodeKinds* = set[TNodeKind]
|
||||||
|
|
||||||
type
|
type
|
||||||
TSymFlag* = enum # 47 flags!
|
TSymFlag* = enum # 48 flags!
|
||||||
sfUsed, # read access of sym (for warnings) or simply used
|
sfUsed, # read access of sym (for warnings) or simply used
|
||||||
sfExported, # symbol is exported from module
|
sfExported, # symbol is exported from module
|
||||||
sfFromGeneric, # symbol is instantiation of a generic; this is needed
|
sfFromGeneric, # symbol is instantiation of a generic; this is needed
|
||||||
|
|
@ -299,6 +299,7 @@ type
|
||||||
sfUsedInFinallyOrExcept # symbol is used inside an 'except' or 'finally'
|
sfUsedInFinallyOrExcept # symbol is used inside an 'except' or 'finally'
|
||||||
sfSingleUsedTemp # For temporaries that we know will only be used once
|
sfSingleUsedTemp # For temporaries that we know will only be used once
|
||||||
sfNoalias # 'noalias' annotation, means C's 'restrict'
|
sfNoalias # 'noalias' annotation, means C's 'restrict'
|
||||||
|
sfEffectsDelayed # an 'effectsDelayed' parameter
|
||||||
|
|
||||||
TSymFlags* = set[TSymFlag]
|
TSymFlags* = set[TSymFlag]
|
||||||
|
|
||||||
|
|
@ -568,6 +569,7 @@ type
|
||||||
# sizeof, alignof, offsetof at CT
|
# sizeof, alignof, offsetof at CT
|
||||||
tfExplicitCallConv
|
tfExplicitCallConv
|
||||||
tfIsConstructor
|
tfIsConstructor
|
||||||
|
tfEffectSystemWorkaround
|
||||||
|
|
||||||
TTypeFlags* = set[TTypeFlag]
|
TTypeFlags* = set[TTypeFlag]
|
||||||
|
|
||||||
|
|
@ -1781,7 +1783,7 @@ proc containsNode*(n: PNode, kinds: TNodeKinds): bool =
|
||||||
|
|
||||||
proc hasSubnodeWith*(n: PNode, kind: TNodeKind): bool =
|
proc hasSubnodeWith*(n: PNode, kind: TNodeKind): bool =
|
||||||
case n.kind
|
case n.kind
|
||||||
of nkEmpty..nkNilLit: result = n.kind == kind
|
of nkEmpty..nkNilLit, nkFormalParams: result = n.kind == kind
|
||||||
else:
|
else:
|
||||||
for i in 0..<n.len:
|
for i in 0..<n.len:
|
||||||
if (n[i].kind == kind) or hasSubnodeWith(n[i], kind):
|
if (n[i].kind == kind) or hasSubnodeWith(n[i], kind):
|
||||||
|
|
|
||||||
|
|
@ -325,7 +325,7 @@ proc skipTrivialIndirections(n: PNode): PNode =
|
||||||
|
|
||||||
proc getPotentialWrites(n: PNode; mutate: bool; result: var seq[PNode]) =
|
proc getPotentialWrites(n: PNode; mutate: bool; result: var seq[PNode]) =
|
||||||
case n.kind:
|
case n.kind:
|
||||||
of nkLiterals, nkIdent: discard
|
of nkLiterals, nkIdent, nkFormalParams: discard
|
||||||
of nkSym:
|
of nkSym:
|
||||||
if mutate: result.add n
|
if mutate: result.add n
|
||||||
of nkAsgn, nkFastAsgn:
|
of nkAsgn, nkFastAsgn:
|
||||||
|
|
@ -354,7 +354,7 @@ proc getPotentialWrites(n: PNode; mutate: bool; result: var seq[PNode]) =
|
||||||
|
|
||||||
proc getPotentialReads(n: PNode; result: var seq[PNode]) =
|
proc getPotentialReads(n: PNode; result: var seq[PNode]) =
|
||||||
case n.kind:
|
case n.kind:
|
||||||
of nkLiterals, nkIdent: discard
|
of nkLiterals, nkIdent, nkFormalParams: discard
|
||||||
of nkSym: result.add n
|
of nkSym: result.add n
|
||||||
else:
|
else:
|
||||||
for s in n:
|
for s in n:
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
# This module handles the parsing of command line arguments.
|
# This module handles the parsing of command line arguments.
|
||||||
from ast import setUseIc
|
|
||||||
|
|
||||||
# We do this here before the 'import' statement so 'defined' does not get
|
# We do this here before the 'import' statement so 'defined' does not get
|
||||||
# confused with 'TGCMode.gcMarkAndSweep' etc.
|
# confused with 'TGCMode.gcMarkAndSweep' etc.
|
||||||
|
|
@ -30,7 +29,7 @@ import
|
||||||
msgs, options, nversion, condsyms, extccomp, platform,
|
msgs, options, nversion, condsyms, extccomp, platform,
|
||||||
wordrecg, nimblecmd, lineinfos, pathutils, pathnorm
|
wordrecg, nimblecmd, lineinfos, pathutils, pathnorm
|
||||||
|
|
||||||
from ast import eqTypeFlags, tfGcSafe, tfNoSideEffect
|
from ast import setUseIc, eqTypeFlags, tfGcSafe, tfNoSideEffect
|
||||||
|
|
||||||
# but some have deps to imported modules. Yay.
|
# but some have deps to imported modules. Yay.
|
||||||
bootSwitch(usedTinyC, hasTinyCBackend, "-d:tinyc")
|
bootSwitch(usedTinyC, hasTinyCBackend, "-d:tinyc")
|
||||||
|
|
|
||||||
|
|
@ -137,3 +137,4 @@ proc initDefines*(symbols: StringTableRef) =
|
||||||
defineSymbol("nimHasDragonBox")
|
defineSymbol("nimHasDragonBox")
|
||||||
defineSymbol("nimHasHintAll")
|
defineSymbol("nimHasHintAll")
|
||||||
defineSymbol("nimHasTrace")
|
defineSymbol("nimHasTrace")
|
||||||
|
defineSymbol("nimHasEffectsOf")
|
||||||
|
|
|
||||||
|
|
@ -1085,10 +1085,11 @@ proc documentEffect(cache: IdentCache; n, x: PNode, effectType: TSpecialWord, id
|
||||||
let actual = s.typ.n[0]
|
let actual = s.typ.n[0]
|
||||||
if actual.len != effectListLen: return
|
if actual.len != effectListLen: return
|
||||||
let real = actual[idx]
|
let real = actual[idx]
|
||||||
|
if real == nil: return
|
||||||
|
let realLen = real.len
|
||||||
# warning: hack ahead:
|
# warning: hack ahead:
|
||||||
var effects = newNodeI(nkBracket, n.info, real.len)
|
var effects = newNodeI(nkBracket, n.info, realLen)
|
||||||
for i in 0..<real.len:
|
for i in 0..<realLen:
|
||||||
var t = typeToString(real[i].typ)
|
var t = typeToString(real[i].typ)
|
||||||
if t.startsWith("ref "): t = substr(t, 4)
|
if t.startsWith("ref "): t = substr(t, 4)
|
||||||
effects[i] = newIdentNode(getIdent(cache, t), n.info)
|
effects[i] = newIdentNode(getIdent(cache, t), n.info)
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,7 @@ type
|
||||||
warnAnyEnumConv = "AnyEnumConv",
|
warnAnyEnumConv = "AnyEnumConv",
|
||||||
warnHoleEnumConv = "HoleEnumConv",
|
warnHoleEnumConv = "HoleEnumConv",
|
||||||
warnCstringConv = "CStringConv",
|
warnCstringConv = "CStringConv",
|
||||||
|
warnEffect = "Effect",
|
||||||
warnUser = "User",
|
warnUser = "User",
|
||||||
# hints
|
# hints
|
||||||
hintSuccess = "Success", hintSuccessX = "SuccessX",
|
hintSuccess = "Success", hintSuccessX = "SuccessX",
|
||||||
|
|
@ -163,6 +164,7 @@ const
|
||||||
warnAnyEnumConv: "$1",
|
warnAnyEnumConv: "$1",
|
||||||
warnHoleEnumConv: "$1",
|
warnHoleEnumConv: "$1",
|
||||||
warnCstringConv: "$1",
|
warnCstringConv: "$1",
|
||||||
|
warnEffect: "$1",
|
||||||
warnUser: "$1",
|
warnUser: "$1",
|
||||||
hintSuccess: "operation successful: $#",
|
hintSuccess: "operation successful: $#",
|
||||||
# keep in sync with `testament.isSuccess`
|
# keep in sync with `testament.isSuccess`
|
||||||
|
|
|
||||||
|
|
@ -25,3 +25,8 @@ define:useStdoutAsStdmsg
|
||||||
@if nimHasWarningObservableStores:
|
@if nimHasWarningObservableStores:
|
||||||
warning:ObservableStores: off
|
warning:ObservableStores: off
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@if nimHasEffectsOf:
|
||||||
|
experimental:strictEffects
|
||||||
|
warningAsError:Effect:on
|
||||||
|
@end
|
||||||
|
|
|
||||||
|
|
@ -204,7 +204,8 @@ type
|
||||||
strictFuncs,
|
strictFuncs,
|
||||||
views,
|
views,
|
||||||
strictNotNil,
|
strictNotNil,
|
||||||
overloadableEnums
|
overloadableEnums,
|
||||||
|
strictEffects
|
||||||
|
|
||||||
LegacyFeature* = enum
|
LegacyFeature* = enum
|
||||||
allowSemcheckedAstModification,
|
allowSemcheckedAstModification,
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ const
|
||||||
wCompilerProc, wNonReloadable, wCore, wProcVar, wVarargs, wCompileTime, wMerge,
|
wCompilerProc, wNonReloadable, wCore, wProcVar, wVarargs, wCompileTime, wMerge,
|
||||||
wBorrow, wImportCompilerProc, wThread,
|
wBorrow, wImportCompilerProc, wThread,
|
||||||
wAsmNoStackFrame, wDiscardable, wNoInit, wCodegenDecl,
|
wAsmNoStackFrame, wDiscardable, wNoInit, wCodegenDecl,
|
||||||
wGensym, wInject, wRaises, wTags, wLocks, wDelegator, wGcSafe,
|
wGensym, wInject, wRaises, wEffectsOf, wTags, wLocks, wDelegator, wGcSafe,
|
||||||
wConstructor, wLiftLocals, wStackTrace, wLineTrace, wNoDestroy,
|
wConstructor, wLiftLocals, wStackTrace, wLineTrace, wNoDestroy,
|
||||||
wRequires, wEnsures}
|
wRequires, wEnsures}
|
||||||
converterPragmas* = procPragmas
|
converterPragmas* = procPragmas
|
||||||
|
|
@ -41,7 +41,7 @@ const
|
||||||
wDiscardable, wGensym, wInject, wDelegator}
|
wDiscardable, wGensym, wInject, wDelegator}
|
||||||
iteratorPragmas* = declPragmas + {FirstCallConv..LastCallConv, wNoSideEffect, wSideEffect,
|
iteratorPragmas* = declPragmas + {FirstCallConv..LastCallConv, wNoSideEffect, wSideEffect,
|
||||||
wMagic, wBorrow,
|
wMagic, wBorrow,
|
||||||
wDiscardable, wGensym, wInject, wRaises,
|
wDiscardable, wGensym, wInject, wRaises, wEffectsOf,
|
||||||
wTags, wLocks, wGcSafe, wRequires, wEnsures}
|
wTags, wLocks, wGcSafe, wRequires, wEnsures}
|
||||||
exprPragmas* = {wLine, wLocks, wNoRewrite, wGcSafe, wNoSideEffect}
|
exprPragmas* = {wLine, wLocks, wNoRewrite, wGcSafe, wNoSideEffect}
|
||||||
stmtPragmas* = {wChecks, wObjChecks, wFieldChecks, wRangeChecks,
|
stmtPragmas* = {wChecks, wObjChecks, wFieldChecks, wRangeChecks,
|
||||||
|
|
@ -59,7 +59,7 @@ const
|
||||||
lambdaPragmas* = {FirstCallConv..LastCallConv,
|
lambdaPragmas* = {FirstCallConv..LastCallConv,
|
||||||
wNoSideEffect, wSideEffect, wNoreturn, wNosinks, wDynlib, wHeader,
|
wNoSideEffect, wSideEffect, wNoreturn, wNosinks, wDynlib, wHeader,
|
||||||
wThread, wAsmNoStackFrame,
|
wThread, wAsmNoStackFrame,
|
||||||
wRaises, wLocks, wTags, wRequires, wEnsures,
|
wRaises, wLocks, wTags, wRequires, wEnsures, wEffectsOf,
|
||||||
wGcSafe, wCodegenDecl, wNoInit, wCompileTime}
|
wGcSafe, wCodegenDecl, wNoInit, wCompileTime}
|
||||||
typePragmas* = declPragmas + {wMagic, wAcyclic,
|
typePragmas* = declPragmas + {wMagic, wAcyclic,
|
||||||
wPure, wHeader, wCompilerProc, wCore, wFinal, wSize, wShallow,
|
wPure, wHeader, wCompilerProc, wCore, wFinal, wSize, wShallow,
|
||||||
|
|
@ -79,7 +79,7 @@ const
|
||||||
paramPragmas* = {wNoalias, wInject, wGensym}
|
paramPragmas* = {wNoalias, wInject, wGensym}
|
||||||
letPragmas* = varPragmas
|
letPragmas* = varPragmas
|
||||||
procTypePragmas* = {FirstCallConv..LastCallConv, wVarargs, wNoSideEffect,
|
procTypePragmas* = {FirstCallConv..LastCallConv, wVarargs, wNoSideEffect,
|
||||||
wThread, wRaises, wLocks, wTags, wGcSafe,
|
wThread, wRaises, wEffectsOf, wLocks, wTags, wGcSafe,
|
||||||
wRequires, wEnsures}
|
wRequires, wEnsures}
|
||||||
forVarPragmas* = {wInject, wGensym}
|
forVarPragmas* = {wInject, wGensym}
|
||||||
allRoutinePragmas* = methodPragmas + iteratorPragmas + lambdaPragmas
|
allRoutinePragmas* = methodPragmas + iteratorPragmas + lambdaPragmas
|
||||||
|
|
@ -779,6 +779,26 @@ proc semCustomPragma(c: PContext, n: PNode): PNode =
|
||||||
# pragma(arg) -> pragma: arg
|
# pragma(arg) -> pragma: arg
|
||||||
result.transitionSonsKind(n.kind)
|
result.transitionSonsKind(n.kind)
|
||||||
|
|
||||||
|
proc processEffectsOf(c: PContext, n: PNode; owner: PSym) =
|
||||||
|
proc processParam(c: PContext; n: PNode) =
|
||||||
|
let r = c.semExpr(c, n)
|
||||||
|
if r.kind == nkSym and r.sym.kind == skParam:
|
||||||
|
if r.sym.owner == owner:
|
||||||
|
incl r.sym.flags, sfEffectsDelayed
|
||||||
|
else:
|
||||||
|
localError(c.config, n.info, errGenerated, "parameter cannot be declared as .effectsOf")
|
||||||
|
else:
|
||||||
|
localError(c.config, n.info, errGenerated, "parameter name expected")
|
||||||
|
|
||||||
|
if n.kind notin nkPragmaCallKinds or n.len != 2:
|
||||||
|
localError(c.config, n.info, errGenerated, "parameter name expected")
|
||||||
|
else:
|
||||||
|
let it = n[1]
|
||||||
|
if it.kind in {nkCurly, nkBracket}:
|
||||||
|
for x in items(it): processParam(c, x)
|
||||||
|
else:
|
||||||
|
processParam(c, it)
|
||||||
|
|
||||||
proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
|
proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
|
||||||
validPragmas: TSpecialWords,
|
validPragmas: TSpecialWords,
|
||||||
comesFromPush, isStatement: bool): bool =
|
comesFromPush, isStatement: bool): bool =
|
||||||
|
|
@ -895,6 +915,8 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
|
||||||
of wNoalias:
|
of wNoalias:
|
||||||
noVal(c, it)
|
noVal(c, it)
|
||||||
incl(sym.flags, sfNoalias)
|
incl(sym.flags, sfNoalias)
|
||||||
|
of wEffectsOf:
|
||||||
|
processEffectsOf(c, it, sym)
|
||||||
of wThreadVar:
|
of wThreadVar:
|
||||||
noVal(c, it)
|
noVal(c, it)
|
||||||
incl(sym.flags, {sfThread, sfGlobal})
|
incl(sym.flags, {sfThread, sfGlobal})
|
||||||
|
|
|
||||||
|
|
@ -400,6 +400,7 @@ when not defined(nimHasSinkInference):
|
||||||
include hlo, seminst, semcall
|
include hlo, seminst, semcall
|
||||||
|
|
||||||
proc resetSemFlag(n: PNode) =
|
proc resetSemFlag(n: PNode) =
|
||||||
|
if n != nil:
|
||||||
excl n.flags, nfSem
|
excl n.flags, nfSem
|
||||||
for i in 0..<n.safeLen:
|
for i in 0..<n.safeLen:
|
||||||
resetSemFlag(n[i])
|
resetSemFlag(n[i])
|
||||||
|
|
|
||||||
|
|
@ -152,6 +152,8 @@ proc effectProblem(f, a: PType; result: var string; c: PContext) =
|
||||||
of efLockLevelsDiffer:
|
of efLockLevelsDiffer:
|
||||||
result.add "\n The `.locks` requirements differ. Annotate the " &
|
result.add "\n The `.locks` requirements differ. Annotate the " &
|
||||||
"proc with {.locks: 0.} to get extended error information."
|
"proc with {.locks: 0.} to get extended error information."
|
||||||
|
of efEffectsDelayed:
|
||||||
|
result.add "\n The `.effectsOf` annotations differ."
|
||||||
when defined(drnim):
|
when defined(drnim):
|
||||||
if not c.graph.compatibleProps(c.graph, f, a):
|
if not c.graph.compatibleProps(c.graph, f, a):
|
||||||
result.add "\n The `.requires` or `.ensures` properties are incompatible."
|
result.add "\n The `.requires` or `.ensures` properties are incompatible."
|
||||||
|
|
|
||||||
|
|
@ -261,7 +261,9 @@ proc getGenSym*(c: PContext; s: PSym): PSym =
|
||||||
result = s
|
result = s
|
||||||
|
|
||||||
proc considerGenSyms*(c: PContext; n: PNode) =
|
proc considerGenSyms*(c: PContext; n: PNode) =
|
||||||
if n.kind == nkSym:
|
if n == nil:
|
||||||
|
discard "can happen for nkFormalParams/nkArgList"
|
||||||
|
elif n.kind == nkSym:
|
||||||
let s = getGenSym(c, n.sym)
|
let s = getGenSym(c, n.sym)
|
||||||
if n.sym != s:
|
if n.sym != s:
|
||||||
n.sym = s
|
n.sym = s
|
||||||
|
|
|
||||||
|
|
@ -296,6 +296,7 @@ proc useVarNoInitCheck(a: PEffects; n: PNode; s: PSym) =
|
||||||
if {sfGlobal, sfThread} * s.flags != {} and s.kind in {skVar, skLet} and
|
if {sfGlobal, sfThread} * s.flags != {} and s.kind in {skVar, skLet} and
|
||||||
s.magic != mNimvm:
|
s.magic != mNimvm:
|
||||||
if s.guard != nil: guardGlobal(a, n, s.guard)
|
if s.guard != nil: guardGlobal(a, n, s.guard)
|
||||||
|
if strictEffects notin a.c.features:
|
||||||
if {sfGlobal, sfThread} * s.flags == {sfGlobal} and
|
if {sfGlobal, sfThread} * s.flags == {sfGlobal} and
|
||||||
(tfHasGCedMem in s.typ.flags or s.typ.isGCedMem):
|
(tfHasGCedMem in s.typ.flags or s.typ.isGCedMem):
|
||||||
#if a.config.hasWarn(warnGcUnsafe): warnAboutGcUnsafe(n)
|
#if a.config.hasWarn(warnGcUnsafe): warnAboutGcUnsafe(n)
|
||||||
|
|
@ -474,14 +475,20 @@ proc trackTryStmt(tracked: PEffects, n: PNode) =
|
||||||
for id, count in items(inter):
|
for id, count in items(inter):
|
||||||
if count == branches: tracked.init.add id
|
if count == branches: tracked.init.add id
|
||||||
|
|
||||||
proc isIndirectCall(n: PNode, owner: PSym): bool =
|
proc isIndirectCall(tracked: PEffects; n: PNode): bool =
|
||||||
# we don't count f(...) as an indirect call if 'f' is an parameter.
|
# we don't count f(...) as an indirect call if 'f' is an parameter.
|
||||||
# Instead we track expressions of type tyProc too. See the manual for
|
# Instead we track expressions of type tyProc too. See the manual for
|
||||||
# details:
|
# details:
|
||||||
if n.kind != nkSym:
|
if n.kind != nkSym:
|
||||||
result = true
|
result = true
|
||||||
elif n.sym.kind == skParam:
|
elif n.sym.kind == skParam:
|
||||||
result = owner != n.sym.owner or owner == nil
|
if strictEffects in tracked.c.features:
|
||||||
|
if tracked.owner == n.sym.owner and sfEffectsDelayed in n.sym.flags:
|
||||||
|
result = false # it is not a harmful call
|
||||||
|
else:
|
||||||
|
result = true
|
||||||
|
else:
|
||||||
|
result = tracked.owner != n.sym.owner or tracked.owner == nil
|
||||||
elif n.sym.kind notin routineKinds:
|
elif n.sym.kind notin routineKinds:
|
||||||
result = true
|
result = true
|
||||||
|
|
||||||
|
|
@ -579,9 +586,14 @@ proc assumeTheWorst(tracked: PEffects; n: PNode; op: PType) =
|
||||||
# message(??.config, n.info, warnUser, "had to assume the worst here")
|
# message(??.config, n.info, warnUser, "had to assume the worst here")
|
||||||
mergeLockLevels(tracked, n, lockLevel)
|
mergeLockLevels(tracked, n, lockLevel)
|
||||||
|
|
||||||
proc isOwnedProcVar(n: PNode; owner: PSym): bool =
|
proc isOwnedProcVar(tracked: PEffects; n: PNode): bool =
|
||||||
# XXX prove the soundness of this effect system rule
|
# XXX prove the soundness of this effect system rule
|
||||||
result = n.kind == nkSym and n.sym.kind == skParam and owner == n.sym.owner
|
result = n.kind == nkSym and n.sym.kind == skParam and
|
||||||
|
tracked.owner == n.sym.owner
|
||||||
|
#if result and sfPolymorphic notin n.sym.flags:
|
||||||
|
# echo tracked.config $ n.info, " different here!"
|
||||||
|
if strictEffects in tracked.c.features:
|
||||||
|
result = result and sfEffectsDelayed in n.sym.flags
|
||||||
|
|
||||||
proc isNoEffectList(n: PNode): bool {.inline.} =
|
proc isNoEffectList(n: PNode): bool {.inline.} =
|
||||||
assert n.kind == nkEffectList
|
assert n.kind == nkEffectList
|
||||||
|
|
@ -590,11 +602,15 @@ proc isNoEffectList(n: PNode): bool {.inline.} =
|
||||||
proc isTrival(caller: PNode): bool {.inline.} =
|
proc isTrival(caller: PNode): bool {.inline.} =
|
||||||
result = caller.kind == nkSym and caller.sym.magic in {mEqProc, mIsNil, mMove, mWasMoved, mSwap}
|
result = caller.kind == nkSym and caller.sym.magic in {mEqProc, mIsNil, mMove, mWasMoved, mSwap}
|
||||||
|
|
||||||
proc trackOperandForIndirectCall(tracked: PEffects, n: PNode, paramType: PType; caller: PNode) =
|
proc trackOperandForIndirectCall(tracked: PEffects, n: PNode, formals: PType; argIndex: int; caller: PNode) =
|
||||||
let a = skipConvCastAndClosure(n)
|
let a = skipConvCastAndClosure(n)
|
||||||
let op = a.typ
|
let op = a.typ
|
||||||
|
let param = if formals != nil and argIndex < formals.len and formals.n != nil: formals.n[argIndex].sym else: nil
|
||||||
# assume indirect calls are taken here:
|
# assume indirect calls are taken here:
|
||||||
if op != nil and op.kind == tyProc and n.skipConv.kind != nkNilLit and not isTrival(caller):
|
if op != nil and op.kind == tyProc and n.skipConv.kind != nkNilLit and
|
||||||
|
not isTrival(caller) and
|
||||||
|
((param != nil and sfEffectsDelayed in param.flags) or strictEffects notin tracked.c.features):
|
||||||
|
|
||||||
internalAssert tracked.config, op.n[0].kind == nkEffectList
|
internalAssert tracked.config, op.n[0].kind == nkEffectList
|
||||||
var effectList = op.n[0]
|
var effectList = op.n[0]
|
||||||
var s = n.skipConv
|
var s = n.skipConv
|
||||||
|
|
@ -607,14 +623,14 @@ proc trackOperandForIndirectCall(tracked: PEffects, n: PNode, paramType: PType;
|
||||||
# we have no explicit effects but it's a forward declaration and so it's
|
# we have no explicit effects but it's a forward declaration and so it's
|
||||||
# stated there are no additional effects, so simply propagate them:
|
# stated there are no additional effects, so simply propagate them:
|
||||||
propagateEffects(tracked, n, n.sym)
|
propagateEffects(tracked, n, n.sym)
|
||||||
elif not isOwnedProcVar(a, tracked.owner):
|
elif not isOwnedProcVar(tracked, a):
|
||||||
# we have no explicit effects so assume the worst:
|
# we have no explicit effects so assume the worst:
|
||||||
assumeTheWorst(tracked, n, op)
|
assumeTheWorst(tracked, n, op)
|
||||||
# assume GcUnsafe unless in its type; 'forward' does not matter:
|
# assume GcUnsafe unless in its type; 'forward' does not matter:
|
||||||
if notGcSafe(op) and not isOwnedProcVar(a, tracked.owner):
|
if notGcSafe(op) and not isOwnedProcVar(tracked, a):
|
||||||
if tracked.config.hasWarn(warnGcUnsafe): warnAboutGcUnsafe(n, tracked.config)
|
if tracked.config.hasWarn(warnGcUnsafe): warnAboutGcUnsafe(n, tracked.config)
|
||||||
markGcUnsafe(tracked, a)
|
markGcUnsafe(tracked, a)
|
||||||
elif tfNoSideEffect notin op.flags and not isOwnedProcVar(a, tracked.owner):
|
elif tfNoSideEffect notin op.flags and not isOwnedProcVar(tracked, a):
|
||||||
markSideEffect(tracked, a, n.info)
|
markSideEffect(tracked, a, n.info)
|
||||||
else:
|
else:
|
||||||
mergeRaises(tracked, effectList[exceptionEffects], n)
|
mergeRaises(tracked, effectList[exceptionEffects], n)
|
||||||
|
|
@ -624,6 +640,7 @@ proc trackOperandForIndirectCall(tracked: PEffects, n: PNode, paramType: PType;
|
||||||
markGcUnsafe(tracked, a)
|
markGcUnsafe(tracked, a)
|
||||||
elif tfNoSideEffect notin op.flags:
|
elif tfNoSideEffect notin op.flags:
|
||||||
markSideEffect(tracked, a, n.info)
|
markSideEffect(tracked, a, n.info)
|
||||||
|
let paramType = if formals != nil and argIndex < formals.len: formals[argIndex] else: nil
|
||||||
if paramType != nil and paramType.kind in {tyVar}:
|
if paramType != nil and paramType.kind in {tyVar}:
|
||||||
invalidateFacts(tracked.guards, n)
|
invalidateFacts(tracked.guards, n)
|
||||||
if n.kind == nkSym and isLocalVar(tracked, n.sym):
|
if n.kind == nkSym and isLocalVar(tracked, n.sym):
|
||||||
|
|
@ -724,9 +741,6 @@ proc trackBlock(tracked: PEffects, n: PNode) =
|
||||||
else:
|
else:
|
||||||
track(tracked, n)
|
track(tracked, n)
|
||||||
|
|
||||||
proc paramType(op: PType, i: int): PType =
|
|
||||||
if op != nil and i < op.len: result = op[i]
|
|
||||||
|
|
||||||
proc cstringCheck(tracked: PEffects; n: PNode) =
|
proc cstringCheck(tracked: PEffects; n: PNode) =
|
||||||
if n[0].typ.kind == tyCstring and (let a = skipConv(n[1]);
|
if n[0].typ.kind == tyCstring and (let a = skipConv(n[1]);
|
||||||
a.typ.kind == tyString and a.kind notin {nkStrLit..nkTripleStrLit}):
|
a.typ.kind == tyString and a.kind notin {nkStrLit..nkTripleStrLit}):
|
||||||
|
|
@ -770,6 +784,25 @@ proc checkRange(c: PEffects; value: PNode; typ: PType) =
|
||||||
checkLe(c, lowBound, value)
|
checkLe(c, lowBound, value)
|
||||||
checkLe(c, value, highBound)
|
checkLe(c, value, highBound)
|
||||||
|
|
||||||
|
#[
|
||||||
|
proc passedToEffectsDelayedParam(tracked: PEffects; n: PNode) =
|
||||||
|
let t = n.typ.skipTypes(abstractInst)
|
||||||
|
if t.kind == tyProc:
|
||||||
|
if n.kind == nkSym and tracked.owner == n.sym.owner and sfEffectsDelayed in n.sym.flags:
|
||||||
|
discard "the arg is itself a delayed parameter, so do nothing"
|
||||||
|
else:
|
||||||
|
var effectList = t.n[0]
|
||||||
|
if effectList.len == effectListLen:
|
||||||
|
mergeRaises(tracked, effectList[exceptionEffects], n)
|
||||||
|
mergeTags(tracked, effectList[tagEffects], n)
|
||||||
|
if not importedFromC(n):
|
||||||
|
if notGcSafe(t):
|
||||||
|
if tracked.config.hasWarn(warnGcUnsafe): warnAboutGcUnsafe(n, tracked.config)
|
||||||
|
markGcUnsafe(tracked, n)
|
||||||
|
if tfNoSideEffect notin t.flags:
|
||||||
|
markSideEffect(tracked, n, n.info)
|
||||||
|
]#
|
||||||
|
|
||||||
proc trackCall(tracked: PEffects; n: PNode) =
|
proc trackCall(tracked: PEffects; n: PNode) =
|
||||||
template gcsafeAndSideeffectCheck() =
|
template gcsafeAndSideeffectCheck() =
|
||||||
if notGcSafe(op) and not importedFromC(a):
|
if notGcSafe(op) and not importedFromC(a):
|
||||||
|
|
@ -811,7 +844,7 @@ proc trackCall(tracked: PEffects; n: PNode) =
|
||||||
elif isNoEffectList(effectList):
|
elif isNoEffectList(effectList):
|
||||||
if isForwardedProc(a):
|
if isForwardedProc(a):
|
||||||
propagateEffects(tracked, n, a.sym)
|
propagateEffects(tracked, n, a.sym)
|
||||||
elif isIndirectCall(a, tracked.owner):
|
elif isIndirectCall(tracked, a):
|
||||||
assumeTheWorst(tracked, n, op)
|
assumeTheWorst(tracked, n, op)
|
||||||
gcsafeAndSideeffectCheck()
|
gcsafeAndSideeffectCheck()
|
||||||
else:
|
else:
|
||||||
|
|
@ -819,7 +852,8 @@ proc trackCall(tracked: PEffects; n: PNode) =
|
||||||
mergeTags(tracked, effectList[tagEffects], n)
|
mergeTags(tracked, effectList[tagEffects], n)
|
||||||
gcsafeAndSideeffectCheck()
|
gcsafeAndSideeffectCheck()
|
||||||
if a.kind != nkSym or a.sym.magic != mNBindSym:
|
if a.kind != nkSym or a.sym.magic != mNBindSym:
|
||||||
for i in 1..<n.len: trackOperandForIndirectCall(tracked, n[i], paramType(op, i), a)
|
for i in 1..<n.len:
|
||||||
|
trackOperandForIndirectCall(tracked, n[i], op, i, a)
|
||||||
if a.kind == nkSym and a.sym.magic in {mNew, mNewFinalize, mNewSeq}:
|
if a.kind == nkSym and a.sym.magic in {mNew, mNewFinalize, mNewSeq}:
|
||||||
# may not look like an assignment, but it is:
|
# may not look like an assignment, but it is:
|
||||||
let arg = n[1]
|
let arg = n[1]
|
||||||
|
|
@ -1225,7 +1259,7 @@ proc subtypeRelation(g: ModuleGraph; spec, real: PNode): bool =
|
||||||
else:
|
else:
|
||||||
return safeInheritanceDiff(g.excType(real), spec.typ) <= 0
|
return safeInheritanceDiff(g.excType(real), spec.typ) <= 0
|
||||||
|
|
||||||
proc checkRaisesSpec(g: ModuleGraph; spec, real: PNode, msg: string, hints: bool;
|
proc checkRaisesSpec(g: ModuleGraph; emitWarnings: bool; spec, real: PNode, msg: string, hints: bool;
|
||||||
effectPredicate: proc (g: ModuleGraph; a, b: PNode): bool {.nimcall.};
|
effectPredicate: proc (g: ModuleGraph; a, b: PNode): bool {.nimcall.};
|
||||||
hintsArg: PNode = nil) =
|
hintsArg: PNode = nil) =
|
||||||
# check that any real exception is listed in 'spec'; mark those as used;
|
# check that any real exception is listed in 'spec'; mark those as used;
|
||||||
|
|
@ -1241,7 +1275,8 @@ proc checkRaisesSpec(g: ModuleGraph; spec, real: PNode, msg: string, hints: bool
|
||||||
pushInfoContext(g.config, spec.info)
|
pushInfoContext(g.config, spec.info)
|
||||||
var rr = if r.kind == nkRaiseStmt: r[0] else: r
|
var rr = if r.kind == nkRaiseStmt: r[0] else: r
|
||||||
while rr.kind in {nkStmtList, nkStmtListExpr} and rr.len > 0: rr = rr.lastSon
|
while rr.kind in {nkStmtList, nkStmtListExpr} and rr.len > 0: rr = rr.lastSon
|
||||||
localError(g.config, r.info, errGenerated, renderTree(rr) & " " & msg & typeToString(r.typ))
|
message(g.config, r.info, if emitWarnings: warnEffect else: errGenerated,
|
||||||
|
renderTree(rr) & " " & msg & typeToString(r.typ))
|
||||||
popInfoContext(g.config)
|
popInfoContext(g.config)
|
||||||
# hint about unnecessarily listed exception types:
|
# hint about unnecessarily listed exception types:
|
||||||
if hints:
|
if hints:
|
||||||
|
|
@ -1258,11 +1293,11 @@ proc checkMethodEffects*(g: ModuleGraph; disp, branch: PSym) =
|
||||||
let p = disp.ast[pragmasPos]
|
let p = disp.ast[pragmasPos]
|
||||||
let raisesSpec = effectSpec(p, wRaises)
|
let raisesSpec = effectSpec(p, wRaises)
|
||||||
if not isNil(raisesSpec):
|
if not isNil(raisesSpec):
|
||||||
checkRaisesSpec(g, raisesSpec, actual[exceptionEffects],
|
checkRaisesSpec(g, false, raisesSpec, actual[exceptionEffects],
|
||||||
"can raise an unlisted exception: ", hints=off, subtypeRelation)
|
"can raise an unlisted exception: ", hints=off, subtypeRelation)
|
||||||
let tagsSpec = effectSpec(p, wTags)
|
let tagsSpec = effectSpec(p, wTags)
|
||||||
if not isNil(tagsSpec):
|
if not isNil(tagsSpec):
|
||||||
checkRaisesSpec(g, tagsSpec, actual[tagEffects],
|
checkRaisesSpec(g, false, tagsSpec, actual[tagEffects],
|
||||||
"can have an unlisted effect: ", hints=off, subtypeRelation)
|
"can have an unlisted effect: ", hints=off, subtypeRelation)
|
||||||
if sfThread in disp.flags and notGcSafe(branch.typ):
|
if sfThread in disp.flags and notGcSafe(branch.typ):
|
||||||
localError(g.config, branch.info, "base method is GC-safe, but '$1' is not" %
|
localError(g.config, branch.info, "base method is GC-safe, but '$1' is not" %
|
||||||
|
|
@ -1283,7 +1318,7 @@ proc checkMethodEffects*(g: ModuleGraph; disp, branch: PSym) =
|
||||||
"base method has lock level $1, but dispatcher has $2" %
|
"base method has lock level $1, but dispatcher has $2" %
|
||||||
[$branch.typ.lockLevel, $disp.typ.lockLevel])
|
[$branch.typ.lockLevel, $disp.typ.lockLevel])
|
||||||
|
|
||||||
proc setEffectsForProcType*(g: ModuleGraph; t: PType, n: PNode) =
|
proc setEffectsForProcType*(g: ModuleGraph; t: PType, n: PNode; s: PSym = nil) =
|
||||||
var effects = t.n[0]
|
var effects = t.n[0]
|
||||||
if t.kind != tyProc or effects.kind != nkEffectList: return
|
if t.kind != tyProc or effects.kind != nkEffectList: return
|
||||||
if n.kind != nkEmpty:
|
if n.kind != nkEmpty:
|
||||||
|
|
@ -1292,9 +1327,14 @@ proc setEffectsForProcType*(g: ModuleGraph; t: PType, n: PNode) =
|
||||||
let raisesSpec = effectSpec(n, wRaises)
|
let raisesSpec = effectSpec(n, wRaises)
|
||||||
if not isNil(raisesSpec):
|
if not isNil(raisesSpec):
|
||||||
effects[exceptionEffects] = raisesSpec
|
effects[exceptionEffects] = raisesSpec
|
||||||
|
elif s != nil and (s.magic != mNone or {sfImportc, sfExportc} * s.flags == {sfImportc}):
|
||||||
|
effects[exceptionEffects] = newNodeI(nkArgList, effects.info)
|
||||||
|
|
||||||
let tagsSpec = effectSpec(n, wTags)
|
let tagsSpec = effectSpec(n, wTags)
|
||||||
if not isNil(tagsSpec):
|
if not isNil(tagsSpec):
|
||||||
effects[tagEffects] = tagsSpec
|
effects[tagEffects] = tagsSpec
|
||||||
|
elif s != nil and (s.magic != mNone or {sfImportc, sfExportc} * s.flags == {sfImportc}):
|
||||||
|
effects[tagEffects] = newNodeI(nkArgList, effects.info)
|
||||||
|
|
||||||
let requiresSpec = propSpec(n, wRequires)
|
let requiresSpec = propSpec(n, wRequires)
|
||||||
if not isNil(requiresSpec):
|
if not isNil(requiresSpec):
|
||||||
|
|
@ -1304,15 +1344,21 @@ proc setEffectsForProcType*(g: ModuleGraph; t: PType, n: PNode) =
|
||||||
effects[ensuresEffects] = ensuresSpec
|
effects[ensuresEffects] = ensuresSpec
|
||||||
|
|
||||||
effects[pragmasEffects] = n
|
effects[pragmasEffects] = n
|
||||||
|
if s != nil and s.magic != mNone:
|
||||||
|
if s.magic != mEcho:
|
||||||
|
t.flags.incl tfNoSideEffect
|
||||||
|
|
||||||
proc initEffects(g: ModuleGraph; effects: PNode; s: PSym; t: var TEffects; c: PContext) =
|
proc rawInitEffects(g: ModuleGraph; effects: PNode) =
|
||||||
newSeq(effects.sons, effectListLen)
|
newSeq(effects.sons, effectListLen)
|
||||||
effects[exceptionEffects] = newNodeI(nkArgList, s.info)
|
effects[exceptionEffects] = newNodeI(nkArgList, effects.info)
|
||||||
effects[tagEffects] = newNodeI(nkArgList, s.info)
|
effects[tagEffects] = newNodeI(nkArgList, effects.info)
|
||||||
effects[requiresEffects] = g.emptyNode
|
effects[requiresEffects] = g.emptyNode
|
||||||
effects[ensuresEffects] = g.emptyNode
|
effects[ensuresEffects] = g.emptyNode
|
||||||
effects[pragmasEffects] = g.emptyNode
|
effects[pragmasEffects] = g.emptyNode
|
||||||
|
|
||||||
|
proc initEffects(g: ModuleGraph; effects: PNode; s: PSym; t: var TEffects; c: PContext) =
|
||||||
|
rawInitEffects(g, effects)
|
||||||
|
|
||||||
t.exc = effects[exceptionEffects]
|
t.exc = effects[exceptionEffects]
|
||||||
t.tags = effects[tagEffects]
|
t.tags = effects[tagEffects]
|
||||||
t.owner = s
|
t.owner = s
|
||||||
|
|
@ -1341,10 +1387,14 @@ proc trackProc*(c: PContext; s: PSym, body: PNode) =
|
||||||
if effects.kind != nkEffectList: return
|
if effects.kind != nkEffectList: return
|
||||||
# effects already computed?
|
# effects already computed?
|
||||||
if not s.hasRealBody: return
|
if not s.hasRealBody: return
|
||||||
if effects.len == effectListLen: return
|
let emitWarnings = tfEffectSystemWorkaround in s.typ.flags
|
||||||
|
if effects.len == effectListLen and not emitWarnings: return
|
||||||
|
|
||||||
|
var inferredEffects = newNodeI(nkEffectList, s.info)
|
||||||
|
|
||||||
var t: TEffects
|
var t: TEffects
|
||||||
initEffects(g, effects, s, t, c)
|
initEffects(g, inferredEffects, s, t, c)
|
||||||
|
rawInitEffects g, effects
|
||||||
track(t, body)
|
track(t, body)
|
||||||
|
|
||||||
if s.kind != skMacro:
|
if s.kind != skMacro:
|
||||||
|
|
@ -1369,17 +1419,21 @@ proc trackProc*(c: PContext; s: PSym, body: PNode) =
|
||||||
let p = s.ast[pragmasPos]
|
let p = s.ast[pragmasPos]
|
||||||
let raisesSpec = effectSpec(p, wRaises)
|
let raisesSpec = effectSpec(p, wRaises)
|
||||||
if not isNil(raisesSpec):
|
if not isNil(raisesSpec):
|
||||||
checkRaisesSpec(g, raisesSpec, t.exc, "can raise an unlisted exception: ",
|
checkRaisesSpec(g, emitWarnings, raisesSpec, t.exc, "can raise an unlisted exception: ",
|
||||||
hints=on, subtypeRelation, hintsArg=s.ast[0])
|
hints=on, subtypeRelation, hintsArg=s.ast[0])
|
||||||
# after the check, use the formal spec:
|
# after the check, use the formal spec:
|
||||||
effects[exceptionEffects] = raisesSpec
|
effects[exceptionEffects] = raisesSpec
|
||||||
|
else:
|
||||||
|
effects[exceptionEffects] = t.exc
|
||||||
|
|
||||||
let tagsSpec = effectSpec(p, wTags)
|
let tagsSpec = effectSpec(p, wTags)
|
||||||
if not isNil(tagsSpec):
|
if not isNil(tagsSpec):
|
||||||
checkRaisesSpec(g, tagsSpec, t.tags, "can have an unlisted effect: ",
|
checkRaisesSpec(g, emitWarnings, tagsSpec, t.tags, "can have an unlisted effect: ",
|
||||||
hints=off, subtypeRelation)
|
hints=off, subtypeRelation)
|
||||||
# after the check, use the formal spec:
|
# after the check, use the formal spec:
|
||||||
effects[tagEffects] = tagsSpec
|
effects[tagEffects] = tagsSpec
|
||||||
|
else:
|
||||||
|
effects[tagEffects] = t.tags
|
||||||
|
|
||||||
let requiresSpec = propSpec(p, wRequires)
|
let requiresSpec = propSpec(p, wRequires)
|
||||||
if not isNil(requiresSpec):
|
if not isNil(requiresSpec):
|
||||||
|
|
|
||||||
|
|
@ -1953,6 +1953,10 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
|
||||||
if not hasProto:
|
if not hasProto:
|
||||||
implicitPragmas(c, s, n.info, validPragmas)
|
implicitPragmas(c, s, n.info, validPragmas)
|
||||||
|
|
||||||
|
if n[pragmasPos].kind != nkEmpty:
|
||||||
|
setEffectsForProcType(c.graph, s.typ, n[pragmasPos], s)
|
||||||
|
s.typ.flags.incl tfEffectSystemWorkaround
|
||||||
|
|
||||||
# To ease macro generation that produce forwarded .async procs we now
|
# To ease macro generation that produce forwarded .async procs we now
|
||||||
# allow a bit redundancy in the pragma declarations. The rule is
|
# allow a bit redundancy in the pragma declarations. The rule is
|
||||||
# a prototype's pragma list must be a superset of the current pragma
|
# a prototype's pragma list must be a superset of the current pragma
|
||||||
|
|
@ -2211,6 +2215,7 @@ proc evalInclude(c: PContext, n: PNode): PNode =
|
||||||
incMod(c, n, it, result)
|
incMod(c, n, it, result)
|
||||||
|
|
||||||
proc setLine(n: PNode, info: TLineInfo) =
|
proc setLine(n: PNode, info: TLineInfo) =
|
||||||
|
if n != nil:
|
||||||
for i in 0..<n.safeLen: setLine(n[i], info)
|
for i in 0..<n.safeLen: setLine(n[i], info)
|
||||||
n.info = info
|
n.info = info
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1364,10 +1364,14 @@ type
|
||||||
efTagsDiffer
|
efTagsDiffer
|
||||||
efTagsUnknown
|
efTagsUnknown
|
||||||
efLockLevelsDiffer
|
efLockLevelsDiffer
|
||||||
|
efEffectsDelayed
|
||||||
|
|
||||||
proc compatibleEffects*(formal, actual: PType): EffectsCompat =
|
proc compatibleEffects*(formal, actual: PType): EffectsCompat =
|
||||||
# for proc type compatibility checking:
|
# for proc type compatibility checking:
|
||||||
assert formal.kind == tyProc and actual.kind == tyProc
|
assert formal.kind == tyProc and actual.kind == tyProc
|
||||||
|
#if tfEffectSystemWorkaround in actual.flags:
|
||||||
|
# return efCompat
|
||||||
|
|
||||||
if formal.n[0].kind != nkEffectList or
|
if formal.n[0].kind != nkEffectList or
|
||||||
actual.n[0].kind != nkEffectList:
|
actual.n[0].kind != nkEffectList:
|
||||||
return efTagsUnknown
|
return efTagsUnknown
|
||||||
|
|
@ -1391,9 +1395,17 @@ proc compatibleEffects*(formal, actual: PType): EffectsCompat =
|
||||||
# spec requires some exception or tag, but we don't know anything:
|
# spec requires some exception or tag, but we don't know anything:
|
||||||
if real.len == 0: return efTagsUnknown
|
if real.len == 0: return efTagsUnknown
|
||||||
let res = compatibleEffectsAux(st, real[tagEffects])
|
let res = compatibleEffectsAux(st, real[tagEffects])
|
||||||
if not res: return efTagsDiffer
|
if not res:
|
||||||
|
#if tfEffectSystemWorkaround notin actual.flags:
|
||||||
|
return efTagsDiffer
|
||||||
if formal.lockLevel.ord < 0 or
|
if formal.lockLevel.ord < 0 or
|
||||||
actual.lockLevel.ord <= formal.lockLevel.ord:
|
actual.lockLevel.ord <= formal.lockLevel.ord:
|
||||||
|
|
||||||
|
for i in 1 ..< min(formal.n.len, actual.n.len):
|
||||||
|
if formal.n[i].sym.flags * {sfEffectsDelayed} != actual.n[i].sym.flags * {sfEffectsDelayed}:
|
||||||
|
result = efEffectsDelayed
|
||||||
|
break
|
||||||
|
|
||||||
result = efCompat
|
result = efCompat
|
||||||
else:
|
else:
|
||||||
result = efLockLevelsDiffer
|
result = efLockLevelsDiffer
|
||||||
|
|
@ -1597,7 +1609,8 @@ proc typeMismatch*(conf: ConfigRef; info: TLineInfo, formal, actual: PType, n: P
|
||||||
msg.add "\n.tag effect is 'any tag allowed'"
|
msg.add "\n.tag effect is 'any tag allowed'"
|
||||||
of efLockLevelsDiffer:
|
of efLockLevelsDiffer:
|
||||||
msg.add "\nlock levels differ"
|
msg.add "\nlock levels differ"
|
||||||
|
of efEffectsDelayed:
|
||||||
|
msg.add "\n.effectsOf annotations differ"
|
||||||
localError(conf, info, msg)
|
localError(conf, info, msg)
|
||||||
|
|
||||||
proc isTupleRecursive(t: PType, cycleDetector: var IntSet): bool =
|
proc isTupleRecursive(t: PType, cycleDetector: var IntSet): bool =
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ type
|
||||||
wMagic = "magic", wThread = "thread", wFinal = "final", wProfiler = "profiler",
|
wMagic = "magic", wThread = "thread", wFinal = "final", wProfiler = "profiler",
|
||||||
wMemTracker = "memtracker", wObjChecks = "objchecks",
|
wMemTracker = "memtracker", wObjChecks = "objchecks",
|
||||||
wIntDefine = "intdefine", wStrDefine = "strdefine", wBoolDefine = "booldefine",
|
wIntDefine = "intdefine", wStrDefine = "strdefine", wBoolDefine = "booldefine",
|
||||||
wCursor = "cursor", wNoalias = "noalias",
|
wCursor = "cursor", wNoalias = "noalias", wEffectsOf = "effectsOf",
|
||||||
|
|
||||||
wImmediate = "immediate", wConstructor = "constructor", wDestructor = "destructor",
|
wImmediate = "immediate", wConstructor = "constructor", wDestructor = "destructor",
|
||||||
wDelegator = "delegator", wOverride = "override", wImportCpp = "importcpp",
|
wDelegator = "delegator", wOverride = "override", wImportCpp = "importcpp",
|
||||||
|
|
|
||||||
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
|
Effect system
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
**Note**: The rules for effect tracking changed with the release of version
|
||||||
|
1.6 of the Nim compiler. This section describes the new rules that are activated
|
||||||
|
via `--experimental:strictEffects`.
|
||||||
|
|
||||||
|
|
||||||
Exception tracking
|
Exception tracking
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
|
@ -4770,35 +4775,24 @@ possibly raised exceptions; the algorithm operates on `p`'s call graph:
|
||||||
1. Every indirect call via some proc type `T` is assumed to
|
1. Every indirect call via some proc type `T` is assumed to
|
||||||
raise `system.Exception` (the base type of the exception hierarchy) and
|
raise `system.Exception` (the base type of the exception hierarchy) and
|
||||||
thus any exception unless `T` has an explicit `raises` list.
|
thus any exception unless `T` has an explicit `raises` list.
|
||||||
However, if the call is of the form `f(...)` where `f` is a parameter of the currently analyzed routine it is ignored. The call is optimistically assumed to have no effect. Rule 2 compensates for this case.
|
However, if the call is of the form `f(...)` where `f` is a parameter of
|
||||||
2. Every expression of some proc type within a call that is not a call
|
the currently analyzed routine it is ignored that is marked as `.effectsOf: f`.
|
||||||
itself (and not nil) is assumed to be called indirectly somehow and thus
|
The call is optimistically assumed to have no effect.
|
||||||
|
Rule 2 compensates for this case.
|
||||||
|
2. Every expression `e` of some proc type within a call that is passed to parameter
|
||||||
|
marked as `.effectsOf` is assumed to be called indirectly and thus
|
||||||
its raises list is added to `p`'s raises list.
|
its raises list is added to `p`'s raises list.
|
||||||
3. Every call to a proc `q` which has an unknown body (due to a forward
|
3. Every call to a proc `q` which has an unknown body (due to a forward
|
||||||
declaration or an `importc` pragma) is assumed to
|
declaration) is assumed to
|
||||||
raise `system.Exception` unless `q` has an explicit `raises` list.
|
raise `system.Exception` unless `q` has an explicit `raises` list.
|
||||||
|
Procs that are `importc`'ed are assumed to have `.raises: []`, unless explicitly
|
||||||
|
declared otherwise.
|
||||||
4. Every call to a method `m` is assumed to
|
4. Every call to a method `m` is assumed to
|
||||||
raise `system.Exception` unless `m` has an explicit `raises` list.
|
raise `system.Exception` unless `m` has an explicit `raises` list.
|
||||||
5. For every other call, the analysis can determine an exact `raises` list.
|
5. For every other call, the analysis can determine an exact `raises` list.
|
||||||
6. For determining a `raises` list, the `raise` and `try` statements
|
6. For determining a `raises` list, the `raise` and `try` statements
|
||||||
of `p` are taken into consideration.
|
of `p` are taken into consideration.
|
||||||
|
|
||||||
Rules 1-2 ensure the following works:
|
|
||||||
|
|
||||||
.. code-block:: nim
|
|
||||||
proc noRaise(x: proc()) {.raises: [].} =
|
|
||||||
# unknown call that might raise anything, but valid:
|
|
||||||
x()
|
|
||||||
|
|
||||||
proc doRaise() {.raises: [IOError].} =
|
|
||||||
raise newException(IOError, "IO")
|
|
||||||
|
|
||||||
proc use() {.raises: [].} =
|
|
||||||
# doesn't compile! Can raise IOError!
|
|
||||||
noRaise(doRaise)
|
|
||||||
|
|
||||||
So in many cases a callback does not cause the compiler to be overly
|
|
||||||
conservative in its effect analysis.
|
|
||||||
|
|
||||||
Exceptions inheriting from `system.Defect` are not tracked with
|
Exceptions inheriting from `system.Defect` are not tracked with
|
||||||
the `.raises: []` exception tracking mechanism. This is more consistent with the
|
the `.raises: []` exception tracking mechanism. This is more consistent with the
|
||||||
|
|
@ -4823,6 +4817,60 @@ with `--panics:on`:option: Defects become unrecoverable errors.
|
||||||
(Since version 1.4 of the language.)
|
(Since version 1.4 of the language.)
|
||||||
|
|
||||||
|
|
||||||
|
EffectsOf annotation
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Rules 1-2 of the exception tracking inference rules (see the previous section)
|
||||||
|
ensure the following works:
|
||||||
|
|
||||||
|
.. code-block:: nim
|
||||||
|
proc weDontRaiseButMaybeTheCallback(callback: proc()) {.raises: [], effectsOf: callback.} =
|
||||||
|
callback()
|
||||||
|
|
||||||
|
proc doRaise() {.raises: [IOError].} =
|
||||||
|
raise newException(IOError, "IO")
|
||||||
|
|
||||||
|
proc use() {.raises: [].} =
|
||||||
|
# doesn't compile! Can raise IOError!
|
||||||
|
weDontRaiseButMaybeTheCallback(doRaise)
|
||||||
|
|
||||||
|
As can be seen from the example, a parameter of type `proc (...)` can be
|
||||||
|
annotated as `.effectsOf`. Such a parameter allows for effect polymorphism:
|
||||||
|
The proc `weDontRaiseButMaybeTheCallback` raises the exceptions
|
||||||
|
that `callback` raises.
|
||||||
|
|
||||||
|
So in many cases a callback does not cause the compiler to be overly
|
||||||
|
conservative in its effect analysis:
|
||||||
|
|
||||||
|
.. code-block:: nim
|
||||||
|
:test: "nim c $1"
|
||||||
|
:status: 1
|
||||||
|
|
||||||
|
{.push warningAsError[Effect]: on.}
|
||||||
|
{.experimental: "strictEffects".}
|
||||||
|
|
||||||
|
import algorithm
|
||||||
|
|
||||||
|
type
|
||||||
|
MyInt = distinct int
|
||||||
|
|
||||||
|
var toSort = @[MyInt 1, MyInt 2, MyInt 3]
|
||||||
|
|
||||||
|
proc cmpN(a, b: MyInt): int =
|
||||||
|
cmp(a.int, b.int)
|
||||||
|
|
||||||
|
proc harmless {.raises: [].} =
|
||||||
|
toSort.sort cmpN
|
||||||
|
|
||||||
|
proc cmpE(a, b: MyInt): int {.raises: [Exception].} =
|
||||||
|
cmp(a.int, b.int)
|
||||||
|
|
||||||
|
proc harmfull {.raises: [].} =
|
||||||
|
# does not compile, `sort` can now raise Exception
|
||||||
|
toSort.sort cmpE
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Tag tracking
|
Tag tracking
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
|
@ -4831,7 +4879,7 @@ is an *effect*. Other effects can also be defined. A user defined effect is a
|
||||||
means to *tag* a routine and to perform checks against this tag:
|
means to *tag* a routine and to perform checks against this tag:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
:test: "nim c $1"
|
:test: "nim c --warningAsError:Effect:on $1"
|
||||||
:status: 1
|
:status: 1
|
||||||
|
|
||||||
type IO = object ## input/output effect
|
type IO = object ## input/output effect
|
||||||
|
|
@ -4848,6 +4896,78 @@ The inference for tag tracking is analogous to the inference for
|
||||||
exception tracking.
|
exception tracking.
|
||||||
|
|
||||||
|
|
||||||
|
Side effects
|
||||||
|
------------
|
||||||
|
|
||||||
|
The `noSideEffect` pragma is used to mark a proc/iterator that can have only
|
||||||
|
side effects through parameters. This means that the proc/iterator only changes locations that are
|
||||||
|
reachable from its parameters and the return value only depends on the
|
||||||
|
parameters. If none of its parameters have the type `var`, `ref`, `ptr`, `cstring`, or `proc`,
|
||||||
|
then no locations are modified.
|
||||||
|
|
||||||
|
In other words, a routine has no side effects if it does not access a threadlocal
|
||||||
|
or global variable and it does not call any routine that has a side effect.
|
||||||
|
|
||||||
|
It is a static error to mark a proc/iterator to have no side effect if the compiler cannot verify this.
|
||||||
|
|
||||||
|
As a special semantic rule, the built-in `debugEcho
|
||||||
|
<system.html#debugEcho,varargs[typed,]>`_ pretends to be free of side effects
|
||||||
|
so that it can be used for debugging routines marked as `noSideEffect`.
|
||||||
|
|
||||||
|
`func` is syntactic sugar for a proc with no side effects:
|
||||||
|
|
||||||
|
.. code-block:: nim
|
||||||
|
func `+` (x, y: int): int
|
||||||
|
|
||||||
|
|
||||||
|
To override the compiler's side effect analysis a `{.noSideEffect.}`
|
||||||
|
`cast` pragma block can be used:
|
||||||
|
|
||||||
|
.. code-block:: nim
|
||||||
|
|
||||||
|
func f() =
|
||||||
|
{.cast(noSideEffect).}:
|
||||||
|
echo "test"
|
||||||
|
|
||||||
|
**Side effects are usually inferred. The inference for side effects is
|
||||||
|
analogous to the inference for exception tracking.**
|
||||||
|
|
||||||
|
|
||||||
|
GC safety effect
|
||||||
|
----------------
|
||||||
|
|
||||||
|
We call a proc `p` `GC safe`:idx: when it doesn't access any global variable
|
||||||
|
that contains GC'ed memory (`string`, `seq`, `ref` or a closure) either
|
||||||
|
directly or indirectly through a call to a GC unsafe proc.
|
||||||
|
|
||||||
|
**The GC safety property is usually inferred. The inference for GC safety is
|
||||||
|
analogous to the inference for exception tracking.**
|
||||||
|
|
||||||
|
The `gcsafe`:idx: annotation can be used to mark a proc to be gcsafe,
|
||||||
|
otherwise this property is inferred by the compiler. Note that `noSideEffect`
|
||||||
|
implies `gcsafe`.
|
||||||
|
|
||||||
|
Routines that are imported from C are always assumed to be `gcsafe`.
|
||||||
|
|
||||||
|
To override the compiler's gcsafety analysis a `{.cast(gcsafe).}` pragma block can
|
||||||
|
be used:
|
||||||
|
|
||||||
|
.. code-block:: nim
|
||||||
|
|
||||||
|
var
|
||||||
|
someGlobal: string = "some string here"
|
||||||
|
perThread {.threadvar.}: string
|
||||||
|
|
||||||
|
proc setPerThread() =
|
||||||
|
{.cast(gcsafe).}:
|
||||||
|
deepCopy(perThread, someGlobal)
|
||||||
|
|
||||||
|
|
||||||
|
See also:
|
||||||
|
|
||||||
|
- `Shared heap memory management <gc.html>`_.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Effects pragma
|
Effects pragma
|
||||||
--------------
|
--------------
|
||||||
|
|
@ -6366,55 +6486,6 @@ This pragma can also take in an optional warning string to relay to developers.
|
||||||
proc thing(x: bool) {.deprecated: "use thong instead".}
|
proc thing(x: bool) {.deprecated: "use thong instead".}
|
||||||
|
|
||||||
|
|
||||||
noSideEffect pragma
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
The `noSideEffect` pragma is used to mark a proc/iterator that can have only
|
|
||||||
side effects through parameters. This means that the proc/iterator only changes locations that are
|
|
||||||
reachable from its parameters and the return value only depends on the
|
|
||||||
parameters. If none of its parameters have the type `var`, `ref`, `ptr`, `cstring`, or `proc`,
|
|
||||||
then no locations are modified.
|
|
||||||
|
|
||||||
It is a static error to mark a proc/iterator to have no side effect if the compiler cannot verify this.
|
|
||||||
|
|
||||||
As a special semantic rule, the built-in `debugEcho
|
|
||||||
<system.html#debugEcho,varargs[typed,]>`_ pretends to be free of side effects
|
|
||||||
so that it can be used for debugging routines marked as `noSideEffect`.
|
|
||||||
|
|
||||||
`func` is syntactic sugar for a proc with no side effects:
|
|
||||||
|
|
||||||
.. code-block:: nim
|
|
||||||
func `+` (x, y: int): int
|
|
||||||
|
|
||||||
|
|
||||||
To override the compiler's side effect analysis a `{.noSideEffect.}`
|
|
||||||
`cast` pragma block can be used:
|
|
||||||
|
|
||||||
.. code-block:: nim
|
|
||||||
|
|
||||||
func f() =
|
|
||||||
{.cast(noSideEffect).}:
|
|
||||||
echo "test"
|
|
||||||
|
|
||||||
When a `noSideEffect` proc has proc params `bar`, whether it can be used inside a `noSideEffect` context
|
|
||||||
depends on what the compiler knows about `bar`:
|
|
||||||
|
|
||||||
.. code-block:: nim
|
|
||||||
:test: "nim c $1"
|
|
||||||
|
|
||||||
func foo(bar: proc(): int): int = bar()
|
|
||||||
var count = 0
|
|
||||||
proc fn1(): int = 1
|
|
||||||
proc fn2(): int = (count.inc; count)
|
|
||||||
func fun1() = discard foo(fn1) # ok because fn1 is inferred as `func`
|
|
||||||
# func fun2() = discard foo(fn2) # would give: Error: 'fun2' can have side effects
|
|
||||||
|
|
||||||
# with callbacks, the compiler is conservative, ie that bar will have side effects
|
|
||||||
var foo2: type(foo) = foo
|
|
||||||
func main() =
|
|
||||||
discard foo(fn1) # ok
|
|
||||||
# discard foo2(fn1) # now this errors
|
|
||||||
|
|
||||||
|
|
||||||
compileTime pragma
|
compileTime pragma
|
||||||
------------------
|
------------------
|
||||||
|
|
@ -7861,6 +7932,10 @@ collected) heap, and sharing of memory is restricted to global variables. This
|
||||||
helps to prevent race conditions. GC efficiency is improved quite a lot,
|
helps to prevent race conditions. GC efficiency is improved quite a lot,
|
||||||
because the GC never has to stop other threads and see what they reference.
|
because the GC never has to stop other threads and see what they reference.
|
||||||
|
|
||||||
|
The only way to create a thread is via `spawn` or
|
||||||
|
`createThread`. The invoked proc must not use `var` parameters nor must
|
||||||
|
any of its parameters contain a `ref` or `closure` type. This enforces
|
||||||
|
the *no heap sharing restriction*.
|
||||||
|
|
||||||
Thread pragma
|
Thread pragma
|
||||||
-------------
|
-------------
|
||||||
|
|
@ -7875,43 +7950,6 @@ A thread proc is passed to `createThread` or `spawn` and invoked
|
||||||
indirectly; so the `thread` pragma implies `procvar`.
|
indirectly; so the `thread` pragma implies `procvar`.
|
||||||
|
|
||||||
|
|
||||||
GC safety
|
|
||||||
---------
|
|
||||||
|
|
||||||
We call a proc `p` `GC safe`:idx: when it doesn't access any global variable
|
|
||||||
that contains GC'ed memory (`string`, `seq`, `ref` or a closure) either
|
|
||||||
directly or indirectly through a call to a GC unsafe proc.
|
|
||||||
|
|
||||||
The `gcsafe`:idx: annotation can be used to mark a proc to be gcsafe,
|
|
||||||
otherwise this property is inferred by the compiler. Note that `noSideEffect`
|
|
||||||
implies `gcsafe`. The only way to create a thread is via `spawn` or
|
|
||||||
`createThread`. The invoked proc must not use `var` parameters nor must
|
|
||||||
any of its parameters contain a `ref` or `closure` type. This enforces
|
|
||||||
the *no heap sharing restriction*.
|
|
||||||
|
|
||||||
Routines that are imported from C are always assumed to be `gcsafe`.
|
|
||||||
To disable the GC-safety checking the `--threadAnalysis:off`:option: command-line
|
|
||||||
switch can be used. This is a temporary workaround to ease the porting effort
|
|
||||||
from old code to the new threading model.
|
|
||||||
|
|
||||||
To override the compiler's gcsafety analysis a `{.cast(gcsafe).}` pragma block can
|
|
||||||
be used:
|
|
||||||
|
|
||||||
.. code-block:: nim
|
|
||||||
|
|
||||||
var
|
|
||||||
someGlobal: string = "some string here"
|
|
||||||
perThread {.threadvar.}: string
|
|
||||||
|
|
||||||
proc setPerThread() =
|
|
||||||
{.cast(gcsafe).}:
|
|
||||||
deepCopy(perThread, someGlobal)
|
|
||||||
|
|
||||||
|
|
||||||
See also:
|
|
||||||
|
|
||||||
- `Shared heap memory management <gc.html>`_.
|
|
||||||
|
|
||||||
|
|
||||||
Threadvar pragma
|
Threadvar pragma
|
||||||
----------------
|
----------------
|
||||||
|
|
|
||||||
|
|
@ -147,8 +147,13 @@ proc reversed*[T](a: openArray[T], first: Natural, last: int): seq[T]
|
||||||
{.inline, deprecated: "use: `reversed(toOpenArray(a, first, last))`".} =
|
{.inline, deprecated: "use: `reversed(toOpenArray(a, first, last))`".} =
|
||||||
reversed(toOpenArray(a, first, last))
|
reversed(toOpenArray(a, first, last))
|
||||||
|
|
||||||
|
when defined(nimHasEffectsOf):
|
||||||
|
{.experimental: "strictEffects".}
|
||||||
|
else:
|
||||||
|
{.pragma: effectsOf.}
|
||||||
|
|
||||||
proc binarySearch*[T, K](a: openArray[T], key: K,
|
proc binarySearch*[T, K](a: openArray[T], key: K,
|
||||||
cmp: proc (x: T, y: K): int {.closure.}): int =
|
cmp: proc (x: T, y: K): int {.closure.}): int {.effectsOf: cmp.} =
|
||||||
## Binary search for `key` in `a`. Return the index of `key` or -1 if not found.
|
## Binary search for `key` in `a`. Return the index of `key` or -1 if not found.
|
||||||
## Assumes that `a` is sorted according to `cmp`.
|
## Assumes that `a` is sorted according to `cmp`.
|
||||||
##
|
##
|
||||||
|
|
@ -210,7 +215,7 @@ const
|
||||||
onlySafeCode = true
|
onlySafeCode = true
|
||||||
|
|
||||||
proc lowerBound*[T, K](a: openArray[T], key: K,
|
proc lowerBound*[T, K](a: openArray[T], key: K,
|
||||||
cmp: proc(x: T, k: K): int {.closure.}): int =
|
cmp: proc(x: T, k: K): int {.closure.}): int {.effectsOf: cmp.} =
|
||||||
## Returns the index of the first element in `a` that is not less than
|
## Returns the index of the first element in `a` that is not less than
|
||||||
## (i.e. greater or equal to) `key`, or last if no such element is found.
|
## (i.e. greater or equal to) `key`, or last if no such element is found.
|
||||||
## In other words if you have a sorted sequence and you call
|
## In other words if you have a sorted sequence and you call
|
||||||
|
|
@ -260,7 +265,7 @@ proc lowerBound*[T](a: openArray[T], key: T): int = lowerBound(a, key, cmp[T])
|
||||||
## * `upperBound proc<#upperBound,openArray[T],T>`_
|
## * `upperBound proc<#upperBound,openArray[T],T>`_
|
||||||
|
|
||||||
proc upperBound*[T, K](a: openArray[T], key: K,
|
proc upperBound*[T, K](a: openArray[T], key: K,
|
||||||
cmp: proc(x: T, k: K): int {.closure.}): int =
|
cmp: proc(x: T, k: K): int {.closure.}): int {.effectsOf: cmp.} =
|
||||||
## Returns the index of the first element in `a` that is greater than
|
## Returns the index of the first element in `a` that is greater than
|
||||||
## `key`, or last if no such element is found.
|
## `key`, or last if no such element is found.
|
||||||
## In other words if you have a sorted sequence and you call
|
## In other words if you have a sorted sequence and you call
|
||||||
|
|
@ -318,7 +323,7 @@ template `<-`(a, b) =
|
||||||
copyMem(addr(a), addr(b), sizeof(T))
|
copyMem(addr(a), addr(b), sizeof(T))
|
||||||
|
|
||||||
proc mergeAlt[T](a, b: var openArray[T], lo, m, hi: int,
|
proc mergeAlt[T](a, b: var openArray[T], lo, m, hi: int,
|
||||||
cmp: proc (x, y: T): int {.closure.}, order: SortOrder) =
|
cmp: proc (x, y: T): int {.closure.}, order: SortOrder) {.effectsOf: cmp.} =
|
||||||
# Optimization: If max(left) <= min(right) there is nothing to do!
|
# Optimization: If max(left) <= min(right) there is nothing to do!
|
||||||
# 1 2 3 4 ## 5 6 7 8
|
# 1 2 3 4 ## 5 6 7 8
|
||||||
# -> O(n) for sorted arrays.
|
# -> O(n) for sorted arrays.
|
||||||
|
|
@ -358,7 +363,7 @@ proc mergeAlt[T](a, b: var openArray[T], lo, m, hi: int,
|
||||||
|
|
||||||
func sort*[T](a: var openArray[T],
|
func sort*[T](a: var openArray[T],
|
||||||
cmp: proc (x, y: T): int {.closure.},
|
cmp: proc (x, y: T): int {.closure.},
|
||||||
order = SortOrder.Ascending) =
|
order = SortOrder.Ascending) {.effectsOf: cmp.} =
|
||||||
## Default Nim sort (an implementation of merge sort). The sorting
|
## Default Nim sort (an implementation of merge sort). The sorting
|
||||||
## is guaranteed to be stable (that is, equal elements stay in the same order)
|
## is guaranteed to be stable (that is, equal elements stay in the same order)
|
||||||
## and the worst case is guaranteed to be O(n log n).
|
## and the worst case is guaranteed to be O(n log n).
|
||||||
|
|
@ -420,7 +425,7 @@ proc sort*[T](a: var openArray[T], order = SortOrder.Ascending) = sort[T](a,
|
||||||
## * `sortedByIt template<#sortedByIt.t,untyped,untyped>`_
|
## * `sortedByIt template<#sortedByIt.t,untyped,untyped>`_
|
||||||
|
|
||||||
proc sorted*[T](a: openArray[T], cmp: proc(x, y: T): int {.closure.},
|
proc sorted*[T](a: openArray[T], cmp: proc(x, y: T): int {.closure.},
|
||||||
order = SortOrder.Ascending): seq[T] =
|
order = SortOrder.Ascending): seq[T] {.effectsOf: cmp.} =
|
||||||
## Returns `a` sorted by `cmp` in the specified `order`.
|
## Returns `a` sorted by `cmp` in the specified `order`.
|
||||||
##
|
##
|
||||||
## **See also:**
|
## **See also:**
|
||||||
|
|
@ -497,7 +502,7 @@ template sortedByIt*(seq1, op: untyped): untyped =
|
||||||
|
|
||||||
func isSorted*[T](a: openArray[T],
|
func isSorted*[T](a: openArray[T],
|
||||||
cmp: proc(x, y: T): int {.closure.},
|
cmp: proc(x, y: T): int {.closure.},
|
||||||
order = SortOrder.Ascending): bool =
|
order = SortOrder.Ascending): bool {.effectsOf: cmp.} =
|
||||||
## Checks to see whether `a` is already sorted in `order`
|
## Checks to see whether `a` is already sorted in `order`
|
||||||
## using `cmp` for the comparison. The parameters are identical
|
## using `cmp` for the comparison. The parameters are identical
|
||||||
## to `sort`. Requires O(n) time.
|
## to `sort`. Requires O(n) time.
|
||||||
|
|
@ -545,7 +550,7 @@ proc isSorted*[T](a: openArray[T], order = SortOrder.Ascending): bool =
|
||||||
proc merge*[T](
|
proc merge*[T](
|
||||||
result: var seq[T],
|
result: var seq[T],
|
||||||
x, y: openArray[T], cmp: proc(x, y: T): int {.closure.}
|
x, y: openArray[T], cmp: proc(x, y: T): int {.closure.}
|
||||||
) {.since: (1, 5, 1).} =
|
) {.since: (1, 5, 1), effectsOf: cmp.} =
|
||||||
## Merges two sorted `openArray`. `x` and `y` are assumed to be sorted.
|
## Merges two sorted `openArray`. `x` and `y` are assumed to be sorted.
|
||||||
## If you do not wish to provide your own `cmp`,
|
## If you do not wish to provide your own `cmp`,
|
||||||
## you may use `system.cmp` or instead call the overloaded
|
## you may use `system.cmp` or instead call the overloaded
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,18 @@ import os
|
||||||
when defined(windows):
|
when defined(windows):
|
||||||
from strutils import replace
|
from strutils import replace
|
||||||
|
|
||||||
|
when defined(nimHasEffectsOf):
|
||||||
|
{.experimental: "strictEffects".}
|
||||||
|
else:
|
||||||
|
{.pragma: effectsOf.}
|
||||||
|
|
||||||
type
|
type
|
||||||
PathEntry* = object
|
PathEntry* = object
|
||||||
kind*: PathComponent
|
kind*: PathComponent
|
||||||
path*: string
|
path*: string
|
||||||
|
|
||||||
iterator walkDirRecFilter*(dir: string, follow: proc(entry: PathEntry): bool = nil,
|
iterator walkDirRecFilter*(dir: string, follow: proc(entry: PathEntry): bool = nil,
|
||||||
relative = false, checkDir = true): PathEntry {.tags: [ReadDirEffect].} =
|
relative = false, checkDir = true): PathEntry {.tags: [ReadDirEffect], effectsOf: follow.} =
|
||||||
## Improved `os.walkDirRec`.
|
## Improved `os.walkDirRec`.
|
||||||
#[
|
#[
|
||||||
note: a yieldFilter isn't needed because caller can filter at call site, without
|
note: a yieldFilter isn't needed because caller can filter at call site, without
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ proc c_fputs(c: cstring, f: File): cint {.
|
||||||
proc c_fgets(c: cstring, n: cint, f: File): cstring {.
|
proc c_fgets(c: cstring, n: cint, f: File): cstring {.
|
||||||
importc: "fgets", header: "<stdio.h>", tags: [ReadIOEffect].}
|
importc: "fgets", header: "<stdio.h>", tags: [ReadIOEffect].}
|
||||||
proc c_fgetc(stream: File): cint {.
|
proc c_fgetc(stream: File): cint {.
|
||||||
importc: "fgetc", header: "<stdio.h>", tags: [ReadIOEffect].}
|
importc: "fgetc", header: "<stdio.h>", tags: [].}
|
||||||
proc c_ungetc(c: cint, f: File): cint {.
|
proc c_ungetc(c: cint, f: File): cint {.
|
||||||
importc: "ungetc", header: "<stdio.h>", tags: [].}
|
importc: "ungetc", header: "<stdio.h>", tags: [].}
|
||||||
proc c_putc(c: cint, stream: File): cint {.
|
proc c_putc(c: cint, stream: File): cint {.
|
||||||
|
|
|
||||||
|
|
@ -261,7 +261,7 @@ proc cpDir*(`from`, to: string) {.raises: [OSError].} =
|
||||||
checkOsError()
|
checkOsError()
|
||||||
|
|
||||||
proc exec*(command: string) {.
|
proc exec*(command: string) {.
|
||||||
raises: [OSError], tags: [ExecIOEffect].} =
|
raises: [OSError], tags: [ExecIOEffect, WriteIOEffect].} =
|
||||||
## Executes an external process. If the external process terminates with
|
## Executes an external process. If the external process terminates with
|
||||||
## a non-zero exit code, an OSError exception is raised.
|
## a non-zero exit code, an OSError exception is raised.
|
||||||
##
|
##
|
||||||
|
|
@ -274,7 +274,7 @@ proc exec*(command: string) {.
|
||||||
checkOsError()
|
checkOsError()
|
||||||
|
|
||||||
proc exec*(command: string, input: string, cache = "") {.
|
proc exec*(command: string, input: string, cache = "") {.
|
||||||
raises: [OSError], tags: [ExecIOEffect].} =
|
raises: [OSError], tags: [ExecIOEffect, WriteIOEffect].} =
|
||||||
## Executes an external process. If the external process terminates with
|
## Executes an external process. If the external process terminates with
|
||||||
## a non-zero exit code, an OSError exception is raised.
|
## a non-zero exit code, an OSError exception is raised.
|
||||||
log "exec: " & command:
|
log "exec: " & command:
|
||||||
|
|
@ -284,7 +284,7 @@ proc exec*(command: string, input: string, cache = "") {.
|
||||||
echo output
|
echo output
|
||||||
|
|
||||||
proc selfExec*(command: string) {.
|
proc selfExec*(command: string) {.
|
||||||
raises: [OSError], tags: [ExecIOEffect].} =
|
raises: [OSError], tags: [ExecIOEffect, WriteIOEffect].} =
|
||||||
## Executes an external command with the current nim/nimble executable.
|
## Executes an external command with the current nim/nimble executable.
|
||||||
## `Command` must not contain the "nim " part.
|
## `Command` must not contain the "nim " part.
|
||||||
let c = selfExe() & " " & command
|
let c = selfExe() & " " & command
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,7 @@ window.addEventListener('DOMContentLoaded', main);
|
||||||
<h1><a class="toc-backref" href="#12">Procs</a></h1>
|
<h1><a class="toc-backref" href="#12">Procs</a></h1>
|
||||||
<dl class="item">
|
<dl class="item">
|
||||||
<div id="foo">
|
<div id="foo">
|
||||||
<dt><pre><span class="Keyword">proc</span> <a href="#foo"><span class="Identifier">foo</span></a><span class="Other">(</span><span class="Other">)</span> {.<span><span class="Other pragmadots">...</span></span><span class="pragmawrap"><span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Other">]</span><span class="Other">,</span> <span class="Identifier">tags</span><span class="Other">:</span> <span class="Other">[</span><span class="Other">]</span></span>.}</pre></dt>
|
<dt><pre><span class="Keyword">proc</span> <a href="#foo"><span class="Identifier">foo</span></a><span class="Other">(</span><span class="Other">)</span> {.<span><span class="Other pragmadots">...</span></span><span class="pragmawrap"><span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Other">]</span><span class="Other">,</span> <span class="Identifier">tags</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">WriteIOEffect</span><span class="Other">]</span></span>.}</pre></dt>
|
||||||
<dd>
|
<dd>
|
||||||
|
|
||||||
I do foo
|
I do foo
|
||||||
|
|
|
||||||
|
|
@ -654,7 +654,7 @@ This is deprecated with a message.
|
||||||
</div>
|
</div>
|
||||||
<div id="c_nonexistent,cstring">
|
<div id="c_nonexistent,cstring">
|
||||||
<dt><pre><span class="Keyword">proc</span> <a href="#c_nonexistent%2Ccstring"><span class="Identifier">c_nonexistent</span></a><span class="Other">(</span><span class="Identifier">frmt</span><span class="Other">:</span> <span class="Identifier">cstring</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">cint</span> {.<span class="Identifier">importc</span><span class="Other">:</span> <span class="StringLit">"nonexistent"</span><span class="Other">,</span>
|
<dt><pre><span class="Keyword">proc</span> <a href="#c_nonexistent%2Ccstring"><span class="Identifier">c_nonexistent</span></a><span class="Other">(</span><span class="Identifier">frmt</span><span class="Other">:</span> <span class="Identifier">cstring</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">cint</span> {.<span class="Identifier">importc</span><span class="Other">:</span> <span class="StringLit">"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>
|
<dd>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -663,7 +663,7 @@ This is deprecated with a message.
|
||||||
</div>
|
</div>
|
||||||
<div id="c_printf,cstring">
|
<div id="c_printf,cstring">
|
||||||
<dt><pre><span class="Keyword">proc</span> <a href="#c_printf%2Ccstring"><span class="Identifier">c_printf</span></a><span class="Other">(</span><span class="Identifier">frmt</span><span class="Other">:</span> <span class="Identifier">cstring</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">cint</span> {.<span class="Identifier">importc</span><span class="Other">:</span> <span class="StringLit">"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>
|
<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>
|
<dd>
|
||||||
|
|
||||||
the c printf. etc.
|
the c printf. etc.
|
||||||
|
|
@ -690,7 +690,8 @@ came form utils but should be shown where <tt class="docutils literal"><span cla
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div id="low2,T">
|
<div id="low2,T">
|
||||||
<dt><pre><span class="Keyword">proc</span> <a href="#low2%2CT"><span class="Identifier">low2</span></a><span class="Other">[</span><span class="Identifier">T</span><span class="Other">:</span> <span class="Identifier">Ordinal</span> <span class="Operator">|</span> <span class="Keyword">enum</span> <span class="Operator">|</span> <span class="Identifier">range</span><span class="Other">]</span><span class="Other">(</span><span class="Identifier">x</span><span class="Other">:</span> <span class="Identifier">T</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">T</span> {.<span class="Identifier">magic</span><span class="Other">:</span> <span class="StringLit">"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>
|
<dd>
|
||||||
|
|
||||||
<p>Returns the lowest possible value of an ordinal value <tt class="docutils literal"><span class="pre"><span class="Identifier">x</span></span></tt>. As a special semantic rule, <tt class="docutils literal"><span class="pre"><span class="Identifier">x</span></span></tt> may also be a type identifier.</p>
|
<p>Returns the lowest possible value of an ordinal value <tt class="docutils literal"><span class="pre"><span class="Identifier">x</span></span></tt>. As a special semantic rule, <tt class="docutils literal"><span class="pre"><span class="Identifier">x</span></span></tt> may also be a type identifier.</p>
|
||||||
|
|
@ -704,7 +705,8 @@ came form utils but should be shown where <tt class="docutils literal"><span cla
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div id="low,T">
|
<div id="low,T">
|
||||||
<dt><pre><span class="Keyword">proc</span> <a href="#low%2CT"><span class="Identifier">low</span></a><span class="Other">[</span><span class="Identifier">T</span><span class="Other">:</span> <span class="Identifier">Ordinal</span> <span class="Operator">|</span> <span class="Keyword">enum</span> <span class="Operator">|</span> <span class="Identifier">range</span><span class="Other">]</span><span class="Other">(</span><span class="Identifier">x</span><span class="Other">:</span> <span class="Identifier">T</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">T</span> {.<span class="Identifier">magic</span><span class="Other">:</span> <span class="StringLit">"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>
|
<dd>
|
||||||
|
|
||||||
<p>Returns the lowest possible value of an ordinal value <tt class="docutils literal"><span class="pre"><span class="Identifier">x</span></span></tt>. As a special semantic rule, <tt class="docutils literal"><span class="pre"><span class="Identifier">x</span></span></tt> may also be a type identifier.</p>
|
<p>Returns the lowest possible value of an ordinal value <tt class="docutils literal"><span class="pre"><span class="Identifier">x</span></span></tt>. As a special semantic rule, <tt class="docutils literal"><span class="pre"><span class="Identifier">x</span></span></tt> may also be a type identifier.</p>
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ teffects1.nim(22, 29) Hint: 'lier' cannot raise 'IO2Error' [XCannotRaiseY]
|
||||||
teffects1.nim(38, 21) Error: type mismatch: got <proc (x: int): string{.noSideEffect, gcsafe, locks: 0.}> but expected 'MyProcType = proc (x: int): string{.closure.}'
|
teffects1.nim(38, 21) Error: type mismatch: got <proc (x: int): string{.noSideEffect, gcsafe, locks: 0.}> but expected 'MyProcType = proc (x: int): string{.closure.}'
|
||||||
.raise effects differ'''
|
.raise effects differ'''
|
||||||
"""
|
"""
|
||||||
|
{.push warningAsError[Effect]: on.}
|
||||||
type
|
type
|
||||||
TObj {.pure, inheritable.} = object
|
TObj {.pure, inheritable.} = object
|
||||||
TObjB = object of TObj
|
TObjB = object of TObj
|
||||||
|
|
@ -41,3 +41,4 @@ type mismatch: got <proc (x: int): string{.noSideEffect, gcsafe, locks: 0.}> but
|
||||||
|
|
||||||
]#
|
]#
|
||||||
{.pop.}
|
{.pop.}
|
||||||
|
{.pop.}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ discard """
|
||||||
errormsg: "can raise an unlisted exception: ref IOError"
|
errormsg: "can raise an unlisted exception: ref IOError"
|
||||||
line: 19
|
line: 19
|
||||||
"""
|
"""
|
||||||
|
{.push warningAsError[Effect]: on.}
|
||||||
type
|
type
|
||||||
TObj = object {.pure, inheritable.}
|
TObj = object {.pure, inheritable.}
|
||||||
TObjB = object of TObj
|
TObjB = object of TObj
|
||||||
|
|
@ -17,3 +17,4 @@ proc lier(): int {.raises: [IOError].} =
|
||||||
|
|
||||||
proc forw: int =
|
proc forw: int =
|
||||||
raise newException(IOError, "arg")
|
raise newException(IOError, "arg")
|
||||||
|
{.pop.}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ discard """
|
||||||
errormsg: "can raise an unlisted exception: ref ValueError"
|
errormsg: "can raise an unlisted exception: ref ValueError"
|
||||||
line: 10
|
line: 10
|
||||||
"""
|
"""
|
||||||
|
{.push warningAsError[Effect]: on.}
|
||||||
proc foo() {.raises: [].} =
|
proc foo() {.raises: [].} =
|
||||||
try:
|
try:
|
||||||
discard
|
discard
|
||||||
|
|
@ -12,3 +12,5 @@ proc foo() {.raises: [].} =
|
||||||
discard
|
discard
|
||||||
|
|
||||||
foo()
|
foo()
|
||||||
|
|
||||||
|
{.pop.}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ discard """
|
||||||
errormsg: "can raise an unlisted exception: Exception"
|
errormsg: "can raise an unlisted exception: Exception"
|
||||||
line: 10
|
line: 10
|
||||||
"""
|
"""
|
||||||
|
{.push warningAsError[Effect]: on.}
|
||||||
proc foo() {.raises: [].} =
|
proc foo() {.raises: [].} =
|
||||||
try:
|
try:
|
||||||
discard
|
discard
|
||||||
|
|
@ -10,3 +10,4 @@ proc foo() {.raises: [].} =
|
||||||
raise
|
raise
|
||||||
|
|
||||||
foo()
|
foo()
|
||||||
|
{.pop.}
|
||||||
|
|
|
||||||
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"
|
file: "tuserpragma2.nim"
|
||||||
line: 11
|
line: 11
|
||||||
"""
|
"""
|
||||||
|
{.push warningAsError[Effect]: on.}
|
||||||
# bug #7216
|
# bug #7216
|
||||||
{.pragma: my_pragma, raises: [].}
|
{.pragma: my_pragma, raises: [].}
|
||||||
|
|
||||||
proc test1 {.my_pragma.} =
|
proc test1 {.my_pragma.} =
|
||||||
raise newException(Exception, "msg")
|
raise newException(Exception, "msg")
|
||||||
|
{.pop.}
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,6 @@ Hi Andreas! How do you feel, Rumpf?
|
||||||
@[0, 2, 1]
|
@[0, 2, 1]
|
||||||
@[0, 1, 2]
|
@[0, 1, 2]
|
||||||
055this should be the casehugh@["(", "+", " 1", " 2", ")"]
|
055this should be the casehugh@["(", "+", " 1", " 2", ")"]
|
||||||
caught a crash!
|
|
||||||
caught a crash!
|
|
||||||
caught a crash!
|
|
||||||
caught a crash!
|
|
||||||
caught a crash!
|
|
||||||
caught a crash!
|
|
||||||
[5]
|
[5]
|
||||||
[4, 5]
|
[4, 5]
|
||||||
[3, 4, 5]
|
[3, 4, 5]
|
||||||
|
|
@ -161,6 +155,8 @@ block tropes:
|
||||||
|
|
||||||
|
|
||||||
block tsegfaults:
|
block tsegfaults:
|
||||||
|
when not defined(arm64):
|
||||||
|
var crashes = 0
|
||||||
proc main =
|
proc main =
|
||||||
try:
|
try:
|
||||||
var x: ptr int
|
var x: ptr int
|
||||||
|
|
@ -170,9 +166,10 @@ block tsegfaults:
|
||||||
except ValueError:
|
except ValueError:
|
||||||
discard
|
discard
|
||||||
except NilAccessDefect:
|
except NilAccessDefect:
|
||||||
echo "caught a crash!"
|
inc crashes
|
||||||
for i in 0..5:
|
for i in 0..5:
|
||||||
main()
|
main()
|
||||||
|
assert crashes == 6
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue