Question

Why can compiled modules contain but not import abstract modules?

Answer

The question refers to code like this:

abstract module J {}

module K {
  abstract module JJ {}
  import J // ERROR
}

It is indeed the case that the abstract module JJ can be declared in non-abstract module K but the abstract module J is not permitted to be imported. This discrepancy is the subject of a proposed change to Dafny rules (cf. issue #2635).

In either cases, however, there are limits on what can be done with an abstract submodule. It is first of all not compiled as part of the enclosing module. Thus it can only be used as the subject of refinement.

That feature is described in the remainder of this FAQ.

The enclosing module may declare an abstract module A and also a non-abstract module B that refines A. A refining module import (import D : C) may only occur in an abstract module itself.

Generally speaking, suppose you have an underspecified module that is imported using ‘:’, as in

abstract module Interface {
  function addSome(n: nat): nat
    ensures addSome(n) > n
}
abstract module Mod {
  import A : Interface
  method m() {
    assert 6 <= A.addSome(5);
  }
}

Here A is abstract because it stands for any concrete module that adheres to the interface declared in Interface (note that the function addSome has no body). Consequently Mod must be abstract as well. Interface is not compilable, but it actually does not need to be declared abstract.

Now we can implement a concrete version of Interface:

module Implementation {
  function addSome(n: nat): nat
    ensures addSome(n) == n + 1
  {
    n + 1
  }
}

Then we can implement the concrete module Mod2 as a refinement of the abstract module Mod:

module Mod2 refines Mod {
  import A = Implementation
  ...
}

Here the module Mod.A, which is an unspecified refinement of Interface inside of Mod, is refined to be the concrete module Implementation inside of Mod2, which is a refinement of Mod.