summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorAndreas Rumpf <rumpf_a@web.de>2021-09-02 12:10:14 +0200
committerGitHub <noreply@github.com>2021-09-02 12:10:14 +0200
commite0ef859130f429df1891e31a85955daa753346b4 (patch)
tree49c680c4e93a321a319e38168dd14483c2a9b71e
parent72fa5833adac590e3c78e2b774cd33f28827594b (diff)
downloadNim-e0ef859130f429df1891e31a85955daa753346b4.tar.gz
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
-rw-r--r--changelog.md17
-rw-r--r--compiler/ast.nim6
-rw-r--r--compiler/ccgcalls.nim4
-rw-r--r--compiler/commands.nim3
-rw-r--r--compiler/condsyms.nim1
-rw-r--r--compiler/docgen.nim7
-rw-r--r--compiler/lineinfos.nim2
-rw-r--r--compiler/nim.cfg5
-rw-r--r--compiler/options.nim3
-rw-r--r--compiler/pragmas.nim30
-rw-r--r--compiler/sem.nim7
-rw-r--r--compiler/semcall.nim2
-rw-r--r--compiler/semdata.nim4
-rw-r--r--compiler/sempass2.nim116
-rw-r--r--compiler/semstmts.nim9
-rw-r--r--compiler/types.nim17
-rw-r--r--compiler/wordrecg.nim2
-rw-r--r--doc/manual.rst252
-rw-r--r--lib/pure/algorithm.nim23
-rw-r--r--lib/pure/times.nim2
-rw-r--r--lib/std/private/globs.nim7
-rw-r--r--lib/system/io.nim2
-rw-r--r--lib/system/nimscript.nim6
-rw-r--r--nimdoc/test_out_index_dot_html/expected/index.html2
-rw-r--r--nimdoc/testproject/expected/testproject.html10
-rw-r--r--tests/effects/teffects1.nim3
-rw-r--r--tests/effects/teffects2.nim3
-rw-r--r--tests/effects/teffects7.nim4
-rw-r--r--tests/effects/teffects8.nim3
-rw-r--r--tests/effects/tstrict_effects.nim27
-rw-r--r--tests/effects/tstrict_effects2.nim28
-rw-r--r--tests/effects/tstrict_effects3.nim17
-rw-r--r--tests/effects/tstrict_effects_sort.nim27
-rw-r--r--tests/pragmas/tuserpragma2.nim3
-rw-r--r--tests/stdlib/tstdlib_various.nim31
35 files changed, 482 insertions, 203 deletions
diff --git a/changelog.md b/changelog.md
index 65ae61833..10824cfc6 100644
--- a/changelog.md
+++ b/changelog.md
@@ -427,6 +427,23 @@
 - On embedded devices `malloc` can now be used instead of `mmap` via `-d:nimAllocPagesViaMalloc`.
   This is only supported for `--gc:orc` or `--gc:arc`.
 
+- The effect system was refined and there is a new `.effectsOf` annotation that does
+  explicitly what was previously done implicitly. See the manual for details.
+  To write code that is portable with older Nim versions, use this idiom:
+
+```nim
+
+when defined(nimHasEffectsOf):
+  {.experimental: "strictEffects".}
+else:
+  {.pragma: effectsOf.}
+
+proc mysort(s: seq; cmp: proc(a, b: T): int) {.effectsOf: cmp.}
+
+```
+
+  To enable the new effect system, use --experimental:strictEffects.
+
 
 ## Compiler changes
 
diff --git a/compiler/ast.nim b/compiler/ast.nim
index 5893a5671..50d048edd 100644
--- a/compiler/ast.nim
+++ b/compiler/ast.nim
@@ -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):
diff --git a/compiler/ccgcalls.nim b/compiler/ccgcalls.nim
index 749a7a3b6..828e666a8 100644
--- a/compiler/ccgcalls.nim
+++ b/compiler/ccgcalls.nim
@@ -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:
diff --git a/compiler/commands.nim b/compiler/commands.nim
index 079717862..9b7e35791 100644
--- a/compiler/commands.nim
+++ b/compiler/commands.nim
@@ -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")
diff --git a/compiler/condsyms.nim b/compiler/condsyms.nim
index 33cd1818b..df355bc26 100644
--- a/compiler/condsyms.nim
+++ b/compiler/condsyms.nim
@@ -137,3 +137,4 @@ proc initDefines*(symbols: StringTableRef) =
   defineSymbol("nimHasDragonBox")
   defineSymbol("nimHasHintAll")
   defineSymbol("nimHasTrace")
+  defineSymbol("nimHasEffectsOf")
diff --git a/compiler/docgen.nim b/compiler/docgen.nim
index 706b54bcd..1acfc7489 100644
--- a/compiler/docgen.nim
+++ b/compiler/docgen.nim
@@ -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)
diff --git a/compiler/lineinfos.nim b/compiler/lineinfos.nim
index fd7ce0c04..8bd5a0890 100644
--- a/compiler/lineinfos.nim
+++ b/compiler/lineinfos.nim
@@ -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`
diff --git a/compiler/nim.cfg b/compiler/nim.cfg
index ec51bd463..d6ac6a937 100644
--- a/compiler/nim.cfg
+++ b/compiler/nim.cfg
@@ -25,3 +25,8 @@ define:useStdoutAsStdmsg
 @if nimHasWarningObservableStores:
   warning:ObservableStores: off
 @end
+
+@if nimHasEffectsOf:
+  experimental:strictEffects
+  warningAsError:Effect:on
+@end
diff --git a/compiler/options.nim b/compiler/options.nim
index e37dea1b6..89a16a49c 100644
--- a/compiler/options.nim
+++ b/compiler/options.nim
@@ -204,7 +204,8 @@ type
     strictFuncs,
     views,
     strictNotNil,
-    overloadableEnums
+    overloadableEnums,
+    strictEffects
 
   LegacyFeature* = enum
     allowSemcheckedAstModification,
diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim
index 6637a8673..e9f52c71f 100644
--- a/compiler/pragmas.nim
+++ b/compiler/pragmas.nim
@@ -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})
diff --git a/compiler/sem.nim b/compiler/sem.nim
index 804325e56..24709cf21 100644
--- a/compiler/sem.nim
+++ b/compiler/sem.nim
@@ -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 =
diff --git a/compiler/semcall.nim b/compiler/semcall.nim
index 2478420f7..a3064788e 100644
--- a/compiler/semcall.nim
+++ b/compiler/semcall.nim
@@ -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."
diff --git a/compiler/semdata.nim b/compiler/semdata.nim
index 0a6d09a56..31affb24f 100644
--- a/compiler/semdata.nim
+++ b/compiler/semdata.nim
@@ -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
diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim
index e2f85dd38..b584bb232 100644
--- a/compiler/sempass2.nim
+++ b/compiler/sempass2.nim
@@ -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):
diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim
index 14dc89781..df0e2778b 100644
--- a/compiler/semstmts.nim
+++ b/compiler/semstmts.nim
@@ -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)
diff --git a/compiler/types.nim b/compiler/types.nim
index f0bd99a17..61f563514 100644
--- a/compiler/types.nim
+++ b/compiler/types.nim
@@ -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 =
diff --git a/compiler/wordrecg.nim b/compiler/wordrecg.nim
index c3625c5f3..6ff68be75 100644
--- a/compiler/wordrecg.nim
+++ b/compiler/wordrecg.nim
@@ -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",
diff --git a/doc/manual.rst b/doc/manual.rst
index 72d50a901..02c688968 100644
--- a/doc/manual.rst
+++ b/doc/manual.rst
@@ -4721,6 +4721,11 @@ and rely on functionality of the `x` object to get exception details.
 Effect system
 =============
 
+**Note**: The rules for effect tracking changed with the release of version
+1.6 of the Nim compiler. This section describes the new rules that are activated
+via `--experimental:strictEffects`.
+
+
 Exception tracking
 ------------------
 
@@ -4770,35 +4775,24 @@ possibly raised exceptions; the algorithm operates on `p`'s call graph:
 1. Every indirect call via some proc type `T` is assumed to
    raise `system.Exception` (the base type of the exception hierarchy) and
    thus any exception unless `T` has an explicit `raises` list.
-   However, if the call is of the form `f(...)` where `f` is a parameter of the currently analyzed routine it is ignored. The call is optimistically assumed to have no effect. Rule 2 compensates for this case.
-2. Every expression of some proc type within a call that is not a call
-   itself (and not nil) is assumed to be called indirectly somehow and thus
+   However, if the call is of the form `f(...)` where `f` is a parameter of
+   the currently analyzed routine it is ignored that is marked as `.effectsOf: f`.
+   The call is optimistically assumed to have no effect.
+   Rule 2 compensates for this case.
+2. Every expression `e` of some proc type within a call that is passed to parameter
+   marked as `.effectsOf` is assumed to be called indirectly and thus
    its raises list is added to `p`'s raises list.
 3. Every call to a proc `q` which has an unknown body (due to a forward
-   declaration or an `importc` pragma) is assumed to
+   declaration) is assumed to
    raise `system.Exception` unless `q` has an explicit `raises` list.
+   Procs that are `importc`'ed are assumed to have `.raises: []`, unless explicitly
+   declared otherwise.
 4. Every call to a method `m` is assumed to
    raise `system.Exception` unless `m` has an explicit `raises` list.
 5. For every other call, the analysis can determine an exact `raises` list.
 6. For determining a `raises` list, the `raise` and `try` statements
    of `p` are taken into consideration.
 
-Rules 1-2 ensure the following works:
-
-.. code-block:: nim
-  proc noRaise(x: proc()) {.raises: [].} =
-    # unknown call that might raise anything, but valid:
-    x()
-
-  proc doRaise() {.raises: [IOError].} =
-    raise newException(IOError, "IO")
-
-  proc use() {.raises: [].} =
-    # doesn't compile! Can raise IOError!
-    noRaise(doRaise)
-
-So in many cases a callback does not cause the compiler to be overly
-conservative in its effect analysis.
 
 Exceptions inheriting from `system.Defect` are not tracked with
 the `.raises: []` exception tracking mechanism. This is more consistent with the
@@ -4823,6 +4817,60 @@ with `--panics:on`:option: Defects become unrecoverable errors.
 (Since version 1.4 of the language.)
 
 
+EffectsOf annotation
+--------------------
+
+Rules 1-2 of the exception tracking inference rules (see the previous section)
+ensure the following works:
+
+.. code-block:: nim
+  proc weDontRaiseButMaybeTheCallback(callback: proc()) {.raises: [], effectsOf: callback.} =
+    callback()
+
+  proc doRaise() {.raises: [IOError].} =
+    raise newException(IOError, "IO")
+
+  proc use() {.raises: [].} =
+    # doesn't compile! Can raise IOError!
+    weDontRaiseButMaybeTheCallback(doRaise)
+
+As can be seen from the example, a parameter of type `proc (...)` can be
+annotated as `.effectsOf`. Such a parameter allows for effect polymorphism:
+The proc `weDontRaiseButMaybeTheCallback` raises the exceptions
+that `callback` raises.
+
+So in many cases a callback does not cause the compiler to be overly
+conservative in its effect analysis:
+
+.. code-block:: nim
+    :test: "nim c $1"
+    :status: 1
+
+  {.push warningAsError[Effect]: on.}
+  {.experimental: "strictEffects".}
+
+  import algorithm
+
+  type
+    MyInt = distinct int
+
+  var toSort = @[MyInt 1, MyInt 2, MyInt 3]
+
+  proc cmpN(a, b: MyInt): int =
+    cmp(a.int, b.int)
+
+  proc harmless {.raises: [].} =
+    toSort.sort cmpN
+
+  proc cmpE(a, b: MyInt): int {.raises: [Exception].} =
+    cmp(a.int, b.int)
+
+  proc harmfull {.raises: [].} =
+    # does not compile, `sort` can now raise Exception
+    toSort.sort cmpE
+
+
+
 Tag tracking
 ------------
 
@@ -4831,7 +4879,7 @@ is an *effect*. Other effects can also be defined. A user defined effect is a
 means to *tag* a routine and to perform checks against this tag:
 
 .. code-block:: nim
-    :test: "nim c $1"
+    :test: "nim c --warningAsError:Effect:on $1"
     :status: 1
 
   type IO = object ## input/output effect
@@ -4848,6 +4896,78 @@ The inference for tag tracking is analogous to the inference for
 exception tracking.
 
 
+Side effects
+------------
+
+The `noSideEffect` pragma is used to mark a proc/iterator that can have only
+side effects through parameters. This means that the proc/iterator only changes locations that are
+reachable from its parameters and the return value only depends on the
+parameters. If none of its parameters have the type `var`, `ref`, `ptr`, `cstring`, or `proc`,
+then no locations are modified.
+
+In other words, a routine has no side effects if it does not access a threadlocal
+or global variable and it does not call any routine that has a side effect.
+
+It is a static error to mark a proc/iterator to have no side effect if the compiler cannot verify this.
+
+As a special semantic rule, the built-in `debugEcho
+<system.html#debugEcho,varargs[typed,]>`_ pretends to be free of side effects
+so that it can be used for debugging routines marked as `noSideEffect`.
+
+`func` is syntactic sugar for a proc with no side effects:
+
+.. code-block:: nim
+  func `+` (x, y: int): int
+
+
+To override the compiler's side effect analysis a `{.noSideEffect.}`
+`cast` pragma block can be used:
+
+.. code-block:: nim
+
+  func f() =
+    {.cast(noSideEffect).}:
+      echo "test"
+
+**Side effects are usually inferred. The inference for side effects is
+analogous to the inference for exception tracking.**
+
+
+GC safety effect
+----------------
+
+We call a proc `p` `GC safe`:idx: when it doesn't access any global variable
+that contains GC'ed memory (`string`, `seq`, `ref` or a closure) either
+directly or indirectly through a call to a GC unsafe proc.
+
+**The GC safety property is usually inferred. The inference for GC safety is
+analogous to the inference for exception tracking.**
+
+The `gcsafe`:idx: annotation can be used to mark a proc to be gcsafe,
+otherwise this property is inferred by the compiler. Note that `noSideEffect`
+implies `gcsafe`.
+
+Routines that are imported from C are always assumed to be `gcsafe`.
+
+To override the compiler's gcsafety analysis a `{.cast(gcsafe).}` pragma block can
+be used:
+
+.. code-block:: nim
+
+  var
+    someGlobal: string = "some string here"
+    perThread {.threadvar.}: string
+
+  proc setPerThread() =
+    {.cast(gcsafe).}:
+      deepCopy(perThread, someGlobal)
+
+
+See also:
+
+- `Shared heap memory management <gc.html>`_.
+
+
 
 Effects pragma
 --------------
@@ -6366,55 +6486,6 @@ This pragma can also take in an optional warning string to relay to developers.
   proc thing(x: bool) {.deprecated: "use thong instead".}
 
 
-noSideEffect pragma
--------------------
-
-The `noSideEffect` pragma is used to mark a proc/iterator that can have only
-side effects through parameters. This means that the proc/iterator only changes locations that are
-reachable from its parameters and the return value only depends on the
-parameters. If none of its parameters have the type `var`, `ref`, `ptr`, `cstring`, or `proc`,
-then no locations are modified.
-
-It is a static error to mark a proc/iterator to have no side effect if the compiler cannot verify this.
-
-As a special semantic rule, the built-in `debugEcho
-<system.html#debugEcho,varargs[typed,]>`_ pretends to be free of side effects
-so that it can be used for debugging routines marked as `noSideEffect`.
-
-`func` is syntactic sugar for a proc with no side effects:
-
-.. code-block:: nim
-  func `+` (x, y: int): int
-
-
-To override the compiler's side effect analysis a `{.noSideEffect.}`
-`cast` pragma block can be used:
-
-.. code-block:: nim
-
-  func f() =
-    {.cast(noSideEffect).}:
-      echo "test"
-
-When a `noSideEffect` proc has proc params `bar`, whether it can be used inside a `noSideEffect` context
-depends on what the compiler knows about `bar`:
-
-.. code-block:: nim
-    :test: "nim c $1"
-
-  func foo(bar: proc(): int): int = bar()
-  var count = 0
-  proc fn1(): int = 1
-  proc fn2(): int = (count.inc; count)
-  func fun1() = discard foo(fn1) # ok because fn1 is inferred as `func`
-  # func fun2() = discard foo(fn2) # would give: Error: 'fun2' can have side effects
-
-  # with callbacks, the compiler is conservative, ie that bar will have side effects
-  var foo2: type(foo) = foo
-  func main() =
-    discard foo(fn1) # ok
-    # discard foo2(fn1) # now this errors
-
 
 compileTime pragma
 ------------------
@@ -7861,6 +7932,10 @@ collected) heap, and sharing of memory is restricted to global variables. This
 helps to prevent race conditions. GC efficiency is improved quite a lot,
 because the GC never has to stop other threads and see what they reference.
 
+The only way to create a thread is via `spawn` or
+`createThread`. The invoked proc must not use `var` parameters nor must
+any of its parameters contain a `ref` or `closure` type. This enforces
+the *no heap sharing restriction*.
 
 Thread pragma
 -------------
@@ -7875,43 +7950,6 @@ A thread proc is passed to `createThread` or `spawn` and invoked
 indirectly; so the `thread` pragma implies `procvar`.
 
 
-GC safety
----------
-
-We call a proc `p` `GC safe`:idx: when it doesn't access any global variable
-that contains GC'ed memory (`string`, `seq`, `ref` or a closure) either
-directly or indirectly through a call to a GC unsafe proc.
-
-The `gcsafe`:idx: annotation can be used to mark a proc to be gcsafe,
-otherwise this property is inferred by the compiler. Note that `noSideEffect`
-implies `gcsafe`. The only way to create a thread is via `spawn` or
-`createThread`. The invoked proc must not use `var` parameters nor must
-any of its parameters contain a `ref` or `closure` type. This enforces
-the *no heap sharing restriction*.
-
-Routines that are imported from C are always assumed to be `gcsafe`.
-To disable the GC-safety checking the `--threadAnalysis:off`:option: command-line
-switch can be used. This is a temporary workaround to ease the porting effort
-from old code to the new threading model.
-
-To override the compiler's gcsafety analysis a `{.cast(gcsafe).}` pragma block can
-be used:
-
-.. code-block:: nim
-
-  var
-    someGlobal: string = "some string here"
-    perThread {.threadvar.}: string
-
-  proc setPerThread() =
-    {.cast(gcsafe).}:
-      deepCopy(perThread, someGlobal)
-
-
-See also:
-
-- `Shared heap memory management <gc.html>`_.
-
 
 Threadvar pragma
 ----------------
diff --git a/lib/pure/algorithm.nim b/lib/pure/algorithm.nim
index c96f599e8..1ddcc9843 100644
--- a/lib/pure/algorithm.nim
+++ b/lib/pure/algorithm.nim
@@ -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]]
diff --git a/lib/pure/times.nim b/lib/pure/times.nim
index b2a22250d..b41bb28b5 100644
--- a/lib/pure/times.nim
+++ b/lib/pure/times.nim
@@ -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 =
diff --git a/lib/std/private/globs.nim b/lib/std/private/globs.nim
index b3726c9c3..190316f93 100644
--- a/lib/std/private/globs.nim
+++ b/lib/std/private/globs.nim
@@ -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
diff --git a/lib/system/io.nim b/lib/system/io.nim
index d8b2ac741..59f070f73 100644
--- a/lib/system/io.nim
+++ b/lib/system/io.nim
@@ -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 {.
diff --git a/lib/system/nimscript.nim b/lib/system/nimscript.nim
index 9ece10e3e..b4554d778 100644
--- a/lib/system/nimscript.nim
+++ b/lib/system/nimscript.nim
@@ -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
diff --git a/nimdoc/test_out_index_dot_html/expected/index.html b/nimdoc/test_out_index_dot_html/expected/index.html
index 3499b5326..458a0394f 100644
--- a/nimdoc/test_out_index_dot_html/expected/index.html
+++ b/nimdoc/test_out_index_dot_html/expected/index.html
@@ -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
diff --git a/nimdoc/testproject/expected/testproject.html b/nimdoc/testproject/expected/testproject.html
index d98b1b00f..dbacb57a7 100644
--- a/nimdoc/testproject/expected/testproject.html
+++ b/nimdoc/testproject/expected/testproject.html
@@ -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>
diff --git a/tests/effects/teffects1.nim b/tests/effects/teffects1.nim
index 66d6a3518..82efefe77 100644
--- a/tests/effects/teffects1.nim
+++ b/tests/effects/teffects1.nim
@@ -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.}
diff --git a/tests/effects/teffects2.nim b/tests/effects/teffects2.nim
index e4b50aba5..ec3c0a887 100644
--- a/tests/effects/teffects2.nim
+++ b/tests/effects/teffects2.nim
@@ -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.}
diff --git a/tests/effects/teffects7.nim b/tests/effects/teffects7.nim
index 73865b18d..9b7fbf5f0 100644
--- a/tests/effects/teffects7.nim
+++ b/tests/effects/teffects7.nim
@@ -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.}
diff --git a/tests/effects/teffects8.nim b/tests/effects/teffects8.nim
index fb3c088d6..359b3a1df 100644
--- a/tests/effects/teffects8.nim
+++ b/tests/effects/teffects8.nim
@@ -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.}
diff --git a/tests/effects/tstrict_effects.nim b/tests/effects/tstrict_effects.nim
new file mode 100644
index 000000000..eee8fb71a
--- /dev/null
+++ b/tests/effects/tstrict_effects.nim
@@ -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
diff --git a/tests/effects/tstrict_effects2.nim b/tests/effects/tstrict_effects2.nim
new file mode 100644
index 000000000..acc0a0540
--- /dev/null
+++ b/tests/effects/tstrict_effects2.nim
@@ -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()
diff --git a/tests/effects/tstrict_effects3.nim b/tests/effects/tstrict_effects3.nim
new file mode 100644
index 000000000..1ab15cc80
--- /dev/null
+++ b/tests/effects/tstrict_effects3.nim
@@ -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()
diff --git a/tests/effects/tstrict_effects_sort.nim b/tests/effects/tstrict_effects_sort.nim
new file mode 100644
index 000000000..8928ed0d3
--- /dev/null
+++ b/tests/effects/tstrict_effects_sort.nim
@@ -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
diff --git a/tests/pragmas/tuserpragma2.nim b/tests/pragmas/tuserpragma2.nim
index ce16c4649..c3f31cd5e 100644
--- a/tests/pragmas/tuserpragma2.nim
+++ b/tests/pragmas/tuserpragma2.nim
@@ -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.}
diff --git a/tests/stdlib/tstdlib_various.nim b/tests/stdlib/tstdlib_various.nim
index b153fd2ba..bc90d6ef4 100644
--- a/tests/stdlib/tstdlib_various.nim
+++ b/tests/stdlib/tstdlib_various.nim
@@ -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