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.
|