summary refs log tree commit diff stats
path: root/doc/manual/effects.txt
blob: 254a43fbb0e284a7b563bfeea8ae5da9a50bd580 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
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.