Skip to content

Better effect tracking for inner routines #435

@Araq

Description

@Araq

Refs: status-im/nim-chronos#226

The problem: Currently code like this does not compile:

{.experimental: "strictEffects".}

proc outer =
  proc inner() {.tags: [].} =
    outer()

  inner()

outer()

The compiler produces:

Error: outer() can have an unlisted effect: RootEffect

When inner is analysed for its effects the effects of outer are
not yet known. The specification/manual currently does not really say
what should happen: outer is not forward declared as it doesn't have
to be, but the intention of the spec clearly was to cover this case via
the rule (3):

  1. Every call to a proc q which has an unknown body (due to a forward
    declaration) is assumed to
    raise system.Exception unless q has an explicit raises list.

So, in order to make the example compile, outer's effects need to be annotated
explicitly:

{.experimental: "strictEffects".}

proc outer {.tags: [].} =
  proc inner() {.tags: [].} =
    outer()

  inner()

outer()

While that might not be that bad, the problem here is that code like in the
initial example used to work for years and only the recent bugfixes in the
.experimental: "strictEffects" mode make it not compile. It's a breaking
change which could be handled more smoothly if we make the compiler smarter.

Proposal: Infer the effects of inner routines in combination with outer routines

The effect inference algorithm could work on outer and inner in lock-step:

When inner calls outer it is assumed that outer has no effects and if that
assumption does not turn out to be true, calls of outer need to be "patched".
Or, to phrase it differently: The interference algorithm infers the effects of outer
by also following inner routines directly, treating them much like templates.

Benefits

  • Less code breakage for code that uses .experimental: "strictEffects" (and this mode
    is about to become the default as it fixes a number of loopholes in Nim's effect
    system.)
  • Code ends up with fewer conservative annotations which can matter for generic code.

Downsides

  • A slightly more complex compiler and language definition.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions