diff options
author | Araq <rumpf_a@web.de> | 2013-05-09 03:20:55 +0200 |
---|---|---|
committer | Araq <rumpf_a@web.de> | 2013-05-09 03:20:55 +0200 |
commit | 2d39a18faa67e3f8c366450cf67405527405a0b0 (patch) | |
tree | 95436b06975a41bfb640f0ca0c3d4e69e812c006 | |
parent | 44c4b945eb8e5255aa128dad2553e290eebdd24b (diff) | |
download | Nim-2d39a18faa67e3f8c366450cf67405527405a0b0.tar.gz |
better effects handling for callbacks
-rw-r--r-- | compiler/sempass2.nim | 24 | ||||
-rw-r--r-- | doc/manual.txt | 30 | ||||
-rw-r--r-- | tests/compile/teffects1.nim | 14 | ||||
-rw-r--r-- | todo.txt | 1 |
4 files changed, 63 insertions, 6 deletions
diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index f43820fa7..9213bd48d 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -174,7 +174,10 @@ proc trackTryStmt(tracked: PEffects, n: PNode) = tracked.bottom = oldBottom proc isIndirectCall(n: PNode): bool = - result = n.kind != nkSym or n.sym.kind notin routineKinds + # 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: + result = n.kind != nkSym or n.sym.kind notin (routineKinds+{skParam}) proc isForwardedProc(n: PNode): bool = result = n.kind == nkSym and sfForward in n.sym.flags @@ -236,6 +239,24 @@ proc propagateEffects(tracked: PEffects, n: PNode, s: PSym) = let tagSpec = effectSpec(pragma, wTags) mergeTags(tracked, tagSpec, n) +proc trackOperand(tracked: PEffects, n: PNode) = + let op = n.typ + if op != nil and op.kind == tyProc and n.kind != nkNilLit: + InternalAssert op.n.sons[0].kind == nkEffectList + var effectList = op.n.sons[0] + let s = n.skipConv + if s.kind == nkSym and s.sym.kind in routineKinds: + propagateEffects(tracked, n, s.sym) + elif effectList.len == 0: + if isForwardedProc(n): + propagateEffects(tracked, n, n.sym) + else: + addEffect(tracked, createRaise(n)) + addTag(tracked, createTag(n)) + else: + mergeEffects(tracked, effectList.sons[exceptionEffects], n) + mergeTags(tracked, effectList.sons[tagEffects], n) + proc track(tracked: PEffects, n: PNode) = case n.kind of nkRaiseStmt: @@ -259,6 +280,7 @@ proc track(tracked: PEffects, n: PNode) = else: mergeEffects(tracked, effectList.sons[exceptionEffects], n) mergeTags(tracked, effectList.sons[tagEffects], n) + for i in 1 .. <len(n): trackOperand(tracked, n.sons[i]) of nkTryStmt: trackTryStmt(tracked, n) return diff --git a/doc/manual.txt b/doc/manual.txt index 132f6d038..22aa22266 100644 --- a/doc/manual.txt +++ b/doc/manual.txt @@ -2909,15 +2909,37 @@ 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.E_Base`` (the base type of the exception hierarchy) and thus any exception unless ``T`` has an explicit ``raises`` list. -2. Every call to a proc ``q`` which has an unknown body (due to a forward + However if the call is of the form ``f(...)`` where ``f`` is a parameter + is ignored. Rule 2 compensates for this case. +2. Every expression of some proc type wihtin a call that is not a call + itself (and not nil) is assumed to be called indirectly somehow 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 raise ``system.E_Base`` unless ``q`` has an explicit ``raises`` list. -3. Every call to a method ``m`` is assumed to +4. Every call to a method ``m`` is assumed to raise ``system.E_Base`` unless ``m`` has an explicit ``raises`` list. -4. For every other call the analysis can determine an exact ``raises`` list. -5. For determining a ``raises`` list, the ``raise`` and ``try`` statements +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:: nimrod + proc noRaise(x: proc()) {.raises: [].} = + # unknown call that might raise anything, but valid: + x() + + proc doRaise() {.raises: [EIO].} = + raise newException(EIO, "IO") + + proc use() = + noRaise(doRaise) + # Here the compiler inferes that EIO can be raised. + +So in many cases a callback does not cause the compiler to be overly +conservative in its effect analysis. + Tag tracking ------------ diff --git a/tests/compile/teffects1.nim b/tests/compile/teffects1.nim index 49af28469..54200f2c3 100644 --- a/tests/compile/teffects1.nim +++ b/tests/compile/teffects1.nim @@ -15,3 +15,17 @@ createMenuItem(s, "Go to definition...", echo("blah") ) + +proc noRaise(x: proc()) {.raises: [].} = + # unknown call that might raise anything, but valid: + x() + +proc doRaise() {.raises: [EIO].} = + raise newException(EIO, "IO") + +proc use*() = + noRaise(doRaise) + # Here the compiler inferes that EIO can be raised. + + +use() diff --git a/todo.txt b/todo.txt index 764ba1e13..2f1105df4 100644 --- a/todo.txt +++ b/todo.txt @@ -28,7 +28,6 @@ version 0.9.4 ============= - macros as type pragmas -- effect propagation for callbacks - provide tool/API to track leaks/object counts - hybrid GC - use big blocks in the allocator |