Here is a different proposal: We make `.writes` an effect as originally
anticipated by the write tracking algorithm. However, instead of basing this
analysis on parameters, we base it on _object fields_.
In the following examples I explicitly wrote the `.writes` effects that
otherwise would have been inferred:
type
Node = ref object
data: string
le, ri: Node
proc m(n: Node) {.writes: [Node.data].} =
n.data = "xyz"
proc depth(n: Node): int {.writes: [].} =
(if n == nil: 0 else: max(depth(n.le), depth(n.ri)) + 1)
proc find(n: Node; key: string): Node {.writes: [].} =
var it = n # you can navigate through the tree iteratively just fine
while it != nil:
if it.data == key: return n
if it.data < key: it = it.le
else: it = it.ri
proc select(a, b: Node): Node {.writes: [].} =
result = if oracle(): a else: b
proc construct(a, b: Node): Node {.writes: [].} =
result = Node(data: "new", le: a, ri: b)
proc harmless(a, b: Node) {.writes: [Node.data].} =
var x = construct(a, b)
x.data = "mutated"
proc harmful(a, b: Node) {.writes: [Node.data].} =
var x = select(a, b)
x.data = "mutated"
Run
Since it's based on object fields and not on parameters the aliasing problems
do not apply, but it's a more conservative analysis as `harmless` also has the
`{.writes: [Node.data].}` effect. However, there is no guessing involved and at
least for the compiler it might work out much better. For example in Nim's
backend we often set `PSym.loc` as the computed mangled name and location, but
we don't update other parts of `PSym`.
Also, the most important effect, "writes nothing" is unaffected by the switch
to object fields.