Effect system ============= Exception tracking ------------------ Nim supports exception tracking. The `raises`:idx: pragma can be used to explicitly define which exceptions a proc/iterator/method/converter is allowed to raise. The compiler verifies this: .. code-block:: nim proc p(what: bool) {.raises: [IOError, OSError].} = if what: raise newException(IOError, "IO") else: raise newException(OSError, "OS") An empty ``raises`` list (``raises: []``) means that no exception may be raised: .. code-block:: nim proc p(): bool {.raises: [].} = try: unsafeCall() result = true except: result = false A ``raises`` list can also be attached to a proc type. This affects type compatibility: .. code-block:: nim type Callback = proc (s: string) {.raises: [IOError].} var c: Callback proc p(x: string) = raise newException(OSError, "OS") c = p # type error For a routine ``p`` the compiler uses inference rules to determine the set of 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 analysed 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 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.Exception`` unless ``q`` has an explicit ``raises`` list. 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. Tag tracking ------------ The exception tracking is part of Nim's `effect system`:idx:. Raising an exception 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 type IO = object ## input/output effect proc readLine(): string {.tags: [IO].} proc no_IO_please() {.tags: [].} = # the compiler prevents this: let x = readLine() A tag has to be a type name. A ``tags`` list - like a ``raises`` list - can also be attached to a proc type. This affects type compatibility. The inference for tag tracking is analogous to the inference for exception tracking. Read/Write tracking ------------------- **Note**: Read/write tracking is not yet implemented! The inference for read/write tracking is analogous to the inference for exception tracking. Effects pragma -------------- The ``effects`` pragma has been designed to assist the programmer with the effects analysis. It is a statement that makes the compiler output all inferred effects up to the ``effects``'s position: .. code-block:: nim proc p(what: bool) = if what: raise newException(IOError, "IO") {.effects.} else: raise newException(OSError, "OS") The compiler produces a hint message that ``IOError`` can be raised. ``OSError`` is not listed as it cannot be raised in the branch the ``effects`` pragma appears in.